/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.method;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.OrderUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
Encapsulates information about an @ControllerAdvice
Spring-managed bean without necessarily requiring it to be instantiated. The findAnnotatedBeans(ApplicationContext)
method can be used to discover such beans. However, a ControllerAdviceBean
may be created from any object, including ones without an @ControllerAdvice
annotation.
Author: Rossen Stoyanchev, Brian Clozel, Juergen Hoeller, Sam Brannen Since: 3.2
/**
* Encapsulates information about an {@link ControllerAdvice @ControllerAdvice}
* Spring-managed bean without necessarily requiring it to be instantiated.
*
* <p>The {@link #findAnnotatedBeans(ApplicationContext)} method can be used to
* discover such beans. However, a {@code ControllerAdviceBean} may be created
* from any object, including ones without an {@code @ControllerAdvice} annotation.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.2
*/
public class ControllerAdviceBean implements Ordered {
Reference to the actual bean instance or a String
representing the bean name. /**
* Reference to the actual bean instance or a {@code String} representing
* the bean name.
*/
private final Object beanOrName;
private final boolean isSingleton;
Reference to the resolved bean instance, potentially lazily retrieved via the BeanFactory
. /**
* Reference to the resolved bean instance, potentially lazily retrieved
* via the {@code BeanFactory}.
*/
@Nullable
private Object resolvedBean;
@Nullable
private final Class<?> beanType;
private final HandlerTypePredicate beanTypePredicate;
@Nullable
private final BeanFactory beanFactory;
@Nullable
private Integer order;
Create a ControllerAdviceBean
using the given bean instance. Params: - bean – the bean instance
/**
* Create a {@code ControllerAdviceBean} using the given bean instance.
* @param bean the bean instance
*/
public ControllerAdviceBean(Object bean) {
Assert.notNull(bean, "Bean must not be null");
this.beanOrName = bean;
this.isSingleton = true;
this.resolvedBean = bean;
this.beanType = ClassUtils.getUserClass(bean.getClass());
this.beanTypePredicate = createBeanTypePredicate(this.beanType);
this.beanFactory = null;
}
Create a ControllerAdviceBean
using the given bean name and BeanFactory
. Params: - beanName – the name of the bean
- beanFactory – a
BeanFactory
to retrieve the bean type initially and later to resolve the actual bean
/**
* Create a {@code ControllerAdviceBean} using the given bean name and
* {@code BeanFactory}.
* @param beanName the name of the bean
* @param beanFactory a {@code BeanFactory} to retrieve the bean type initially
* and later to resolve the actual bean
*/
public ControllerAdviceBean(String beanName, BeanFactory beanFactory) {
this(beanName, beanFactory, null);
}
Create a ControllerAdviceBean
using the given bean name, BeanFactory
, and @ControllerAdvice
annotation. Params: - beanName – the name of the bean
- beanFactory – a
BeanFactory
to retrieve the bean type initially and later to resolve the actual bean - controllerAdvice – the
@ControllerAdvice
annotation for the bean, or null
if not yet retrieved
Since: 5.2
/**
* Create a {@code ControllerAdviceBean} using the given bean name,
* {@code BeanFactory}, and {@link ControllerAdvice @ControllerAdvice}
* annotation.
* @param beanName the name of the bean
* @param beanFactory a {@code BeanFactory} to retrieve the bean type initially
* and later to resolve the actual bean
* @param controllerAdvice the {@code @ControllerAdvice} annotation for the
* bean, or {@code null} if not yet retrieved
* @since 5.2
*/
public ControllerAdviceBean(String beanName, BeanFactory beanFactory, @Nullable ControllerAdvice controllerAdvice) {
Assert.hasText(beanName, "Bean name must contain text");
Assert.notNull(beanFactory, "BeanFactory must not be null");
Assert.isTrue(beanFactory.containsBean(beanName), () -> "BeanFactory [" + beanFactory +
"] does not contain specified controller advice bean '" + beanName + "'");
this.beanOrName = beanName;
this.isSingleton = beanFactory.isSingleton(beanName);
this.beanType = getBeanType(beanName, beanFactory);
this.beanTypePredicate = (controllerAdvice != null ? createBeanTypePredicate(controllerAdvice) :
createBeanTypePredicate(this.beanType));
this.beanFactory = beanFactory;
}
Get the order value for the contained bean.
As of Spring Framework 5.3, the order value is lazily retrieved using the following algorithm and cached. Note, however, that a @ControllerAdvice
bean that is configured as a scoped bean — for example, as a request-scoped or session-scoped bean — will not be eagerly resolved. Consequently, Ordered
is not honored for scoped @ControllerAdvice
beans.
- If the resolved bean implements
Ordered
, use the value returned by Ordered.getOrder()
.
- If the factory method is known, use the value returned by
OrderUtils.getOrder(AnnotatedElement)
. - If the bean type is known, use the value returned by
OrderUtils.getOrder(Class<?>, int)
with Ordered.LOWEST_PRECEDENCE
used as the default order value.
- Otherwise use
Ordered.LOWEST_PRECEDENCE
as the default, fallback order value.
See Also:
/**
* Get the order value for the contained bean.
* <p>As of Spring Framework 5.3, the order value is lazily retrieved using
* the following algorithm and cached. Note, however, that a
* {@link ControllerAdvice @ControllerAdvice} bean that is configured as a
* scoped bean — for example, as a request-scoped or session-scoped
* bean — will not be eagerly resolved. Consequently, {@link Ordered} is
* not honored for scoped {@code @ControllerAdvice} beans.
* <ul>
* <li>If the {@linkplain #resolveBean resolved bean} implements {@link Ordered},
* use the value returned by {@link Ordered#getOrder()}.</li>
* <li>If the {@linkplain org.springframework.context.annotation.Bean factory method}
* is known, use the value returned by {@link OrderUtils#getOrder(AnnotatedElement)}.
* <li>If the {@linkplain #getBeanType() bean type} is known, use the value returned
* by {@link OrderUtils#getOrder(Class, int)} with {@link Ordered#LOWEST_PRECEDENCE}
* used as the default order value.</li>
* <li>Otherwise use {@link Ordered#LOWEST_PRECEDENCE} as the default, fallback
* order value.</li>
* </ul>
* @see #resolveBean()
*/
@Override
public int getOrder() {
if (this.order == null) {
String beanName = null;
Object resolvedBean = null;
if (this.beanFactory != null && this.beanOrName instanceof String) {
beanName = (String) this.beanOrName;
String targetBeanName = ScopedProxyUtils.getTargetBeanName(beanName);
boolean isScopedProxy = this.beanFactory.containsBean(targetBeanName);
// Avoid eager @ControllerAdvice bean resolution for scoped proxies,
// since attempting to do so during context initialization would result
// in an exception due to the current absence of the scope. For example,
// an HTTP request or session scope is not active during initialization.
if (!isScopedProxy && !ScopedProxyUtils.isScopedTarget(beanName)) {
resolvedBean = resolveBean();
}
}
else {
resolvedBean = resolveBean();
}
if (resolvedBean instanceof Ordered) {
this.order = ((Ordered) resolvedBean).getOrder();
}
else {
if (beanName != null && this.beanFactory instanceof ConfigurableBeanFactory) {
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) this.beanFactory;
try {
BeanDefinition bd = cbf.getMergedBeanDefinition(beanName);
if (bd instanceof RootBeanDefinition) {
Method factoryMethod = ((RootBeanDefinition) bd).getResolvedFactoryMethod();
if (factoryMethod != null) {
this.order = OrderUtils.getOrder(factoryMethod);
}
}
}
catch (NoSuchBeanDefinitionException ex) {
// ignore -> probably a manually registered singleton
}
}
if (this.order == null) {
if (this.beanType != null) {
this.order = OrderUtils.getOrder(this.beanType, Ordered.LOWEST_PRECEDENCE);
}
else {
this.order = Ordered.LOWEST_PRECEDENCE;
}
}
}
}
return this.order;
}
Return the type of the contained bean.
If the bean type is a CGLIB-generated class, the original user-defined
class is returned.
/**
* Return the type of the contained bean.
* <p>If the bean type is a CGLIB-generated class, the original user-defined
* class is returned.
*/
@Nullable
public Class<?> getBeanType() {
return this.beanType;
}
Get the bean instance for this ControllerAdviceBean
, if necessary resolving the bean name through the BeanFactory
. As of Spring Framework 5.2, once the bean instance has been resolved it will be cached if it is a singleton, thereby avoiding repeated lookups in the BeanFactory
.
/**
* Get the bean instance for this {@code ControllerAdviceBean}, if necessary
* resolving the bean name through the {@link BeanFactory}.
* <p>As of Spring Framework 5.2, once the bean instance has been resolved it
* will be cached if it is a singleton, thereby avoiding repeated lookups in
* the {@code BeanFactory}.
*/
public Object resolveBean() {
if (this.resolvedBean == null) {
// this.beanOrName must be a String representing the bean name if
// this.resolvedBean is null.
Object resolvedBean = obtainBeanFactory().getBean((String) this.beanOrName);
// Don't cache non-singletons (e.g., prototypes).
if (!this.isSingleton) {
return resolvedBean;
}
this.resolvedBean = resolvedBean;
}
return this.resolvedBean;
}
private BeanFactory obtainBeanFactory() {
Assert.state(this.beanFactory != null, "No BeanFactory set");
return this.beanFactory;
}
Check whether the given bean type should be advised by this ControllerAdviceBean
. Params: - beanType – the type of the bean to check
See Also: Since: 4.0
/**
* Check whether the given bean type should be advised by this
* {@code ControllerAdviceBean}.
* @param beanType the type of the bean to check
* @since 4.0
* @see ControllerAdvice
*/
public boolean isApplicableToBeanType(@Nullable Class<?> beanType) {
return this.beanTypePredicate.test(beanType);
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof ControllerAdviceBean)) {
return false;
}
ControllerAdviceBean otherAdvice = (ControllerAdviceBean) other;
return (this.beanOrName.equals(otherAdvice.beanOrName) && this.beanFactory == otherAdvice.beanFactory);
}
@Override
public int hashCode() {
return this.beanOrName.hashCode();
}
@Override
public String toString() {
return this.beanOrName.toString();
}
Find beans annotated with @ControllerAdvice
in the given ApplicationContext
and wrap them as ControllerAdviceBean
instances. As of Spring Framework 5.2, the ControllerAdviceBean
instances in the returned list are sorted using OrderComparator.sort(List<?>)
.
See Also:
/**
* Find beans annotated with {@link ControllerAdvice @ControllerAdvice} in the
* given {@link ApplicationContext} and wrap them as {@code ControllerAdviceBean}
* instances.
* <p>As of Spring Framework 5.2, the {@code ControllerAdviceBean} instances
* in the returned list are sorted using {@link OrderComparator#sort(List)}.
* @see #getOrder()
* @see OrderComparator
* @see Ordered
*/
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
ListableBeanFactory beanFactory = context;
if (context instanceof ConfigurableApplicationContext) {
// Use internal BeanFactory for potential downcast to ConfigurableBeanFactory above
beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory();
}
List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Object.class)) {
if (!ScopedProxyUtils.isScopedTarget(name)) {
ControllerAdvice controllerAdvice = beanFactory.findAnnotationOnBean(name, ControllerAdvice.class);
if (controllerAdvice != null) {
// Use the @ControllerAdvice annotation found by findAnnotationOnBean()
// in order to avoid a subsequent lookup of the same annotation.
adviceBeans.add(new ControllerAdviceBean(name, beanFactory, controllerAdvice));
}
}
}
OrderComparator.sort(adviceBeans);
return adviceBeans;
}
@Nullable
private static Class<?> getBeanType(String beanName, BeanFactory beanFactory) {
Class<?> beanType = beanFactory.getType(beanName);
return (beanType != null ? ClassUtils.getUserClass(beanType) : null);
}
private static HandlerTypePredicate createBeanTypePredicate(@Nullable Class<?> beanType) {
ControllerAdvice controllerAdvice = (beanType != null ?
AnnotatedElementUtils.findMergedAnnotation(beanType, ControllerAdvice.class) : null);
return createBeanTypePredicate(controllerAdvice);
}
private static HandlerTypePredicate createBeanTypePredicate(@Nullable ControllerAdvice controllerAdvice) {
if (controllerAdvice != null) {
return HandlerTypePredicate.builder()
.basePackage(controllerAdvice.basePackages())
.basePackageClass(controllerAdvice.basePackageClasses())
.assignableType(controllerAdvice.assignableTypes())
.annotation(controllerAdvice.annotations())
.build();
}
return HandlerTypePredicate.forAnyHandlerType();
}
}