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

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

The newest version!
/*
 * Copyright 2002-2024 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.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.
 *
 * 

This class is internal to Spring Framework and is not meant to be used * by applications to manually create {@code @ControllerAdvice} beans. * * @author Rossen Stoyanchev * @author Brian Clozel * @author Juergen Hoeller * @author Sam Brannen * @since 3.2 */ public class ControllerAdviceBean implements Ordered { private final String beanName; 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; private final BeanFactory beanFactory; @Nullable private Integer order; /** * 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 * @since 5.2 */ public ControllerAdviceBean(String beanName, BeanFactory beanFactory, 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 + "'"); Assert.notNull(controllerAdvice, "ControllerAdvice must not be null"); this.beanName = beanName; this.isSingleton = beanFactory.isSingleton(beanName); this.beanType = getBeanType(beanName, beanFactory); this.beanTypePredicate = createBeanTypePredicate(controllerAdvice); 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) { Object resolvedBean = null; String targetBeanName = ScopedProxyUtils.getTargetBeanName(this.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(this.beanName)) { resolvedBean = resolveBean(); } if (resolvedBean instanceof Ordered ordered) { this.order = ordered.getOrder(); } else { if (this.beanFactory instanceof ConfigurableBeanFactory cbf) { try { BeanDefinition bd = cbf.getMergedBeanDefinition(this.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}. *

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) { Object resolvedBean = this.beanFactory.getBean(this.beanName); // Don't cache non-singletons (for example, prototypes). if (!this.isSingleton) { return resolvedBean; } this.resolvedBean = resolvedBean; } return this.resolvedBean; } /** * 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.beanName.equals(that.beanName) && this.beanFactory == that.beanFactory)); } @Override public int hashCode() { return this.beanName.hashCode(); } @Override public String toString() { return this.beanName; } /** * Find beans annotated with {@link ControllerAdvice @ControllerAdvice} in the * given {@link ApplicationContext} and wrap them as {@code ControllerAdviceBean} * instances. *

Note that 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(ControllerAdvice controllerAdvice) { return HandlerTypePredicate.builder() .basePackage(controllerAdvice.basePackages()) .basePackageClass(controllerAdvice.basePackageClasses()) .assignableType(controllerAdvice.assignableTypes()) .annotation(controllerAdvice.annotations()) .build(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy