All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.web.method.ControllerAdviceBean Maven / Gradle / Ivy

There is a newer version: 6.2.0
Show newest version
/*
 * Copyright 2002-2023 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 {@link ControllerAdvice @ControllerAdvice}
 * Spring-managed bean without necessarily requiring it to be instantiated.
 *
 * 

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 {@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 {@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 {@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 {@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 {@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 * {@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. *

    *
  • If the {@linkplain #resolveBean resolved bean} implements {@link Ordered}, * use the value returned by {@link Ordered#getOrder()}.
  • *
  • If the {@linkplain org.springframework.context.annotation.Bean factory method} * is known, use the value returned by {@link OrderUtils#getOrder(AnnotatedElement)}. *
  • 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.
  • *
  • Otherwise use {@link Ordered#LOWEST_PRECEDENCE} as the default, fallback * order value.
  • *
* @see #resolveBean() */ @Override public int getOrder() { if (this.order == null) { String beanName = null; Object resolvedBean = null; if (this.beanFactory != null && this.beanOrName instanceof String stringBeanName) { beanName = stringBeanName; 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 ordered) { this.order = ordered.getOrder(); } else { if (beanName != null && this.beanFactory instanceof ConfigurableBeanFactory cbf) { try { BeanDefinition bd = cbf.getMergedBeanDefinition(beanName); if (bd instanceof RootBeanDefinition rbd) { Method factoryMethod = rbd.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. */ @Nullable public Class getBeanType() { return this.beanType; } /** * Get the bean instance for this {@code ControllerAdviceBean}, if necessary * resolving the bean name through the {@link 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 {@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 * {@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) { return (this == other || (other instanceof ControllerAdviceBean that && this.beanOrName.equals(that.beanOrName) && this.beanFactory == that.beanFactory)); } @Override public int hashCode() { return this.beanOrName.hashCode(); } @Override public String toString() { return this.beanOrName.toString(); } /** * Find beans annotated with {@link ControllerAdvice @ControllerAdvice} in the * given {@link ApplicationContext} and wrap them as {@code ControllerAdviceBean} * instances. *

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 findAnnotatedBeans(ApplicationContext context) { ListableBeanFactory beanFactory = context; if (context instanceof ConfigurableApplicationContext cac) { // Use internal BeanFactory for potential downcast to ConfigurableBeanFactory above beanFactory = cac.getBeanFactory(); } List 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(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy