org.springframework.web.method.ControllerAdviceBean.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.web.method.ControllerAdviceBean.java

Source

/*
 * Copyright 2002-2019 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.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
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.
 *
 * <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 {@code String} representing
     * the bean name.
     */
    private final Object beanOrName;

    /**
     * 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.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.beanType = getBeanType(beanName, beanFactory);
        this.beanTypePredicate = (controllerAdvice != null ? createBeanTypePredicate(controllerAdvice)
                : createBeanTypePredicate(this.beanType));
        this.beanFactory = beanFactory;
    }

    /**
     * Get the order value for the contained bean.
     * <p>As of Spring Framework 5.2, the order value is lazily retrieved using
     * the following algorithm and cached.
     * <ul>
     * <li>If the {@linkplain #resolveBean resolved bean} implements {@link Ordered},
     * use the value returned by {@link Ordered#getOrder()}.</li>
     * <li>Otherwise use the value returned by {@link OrderUtils#getOrder(Class, int)}
     * with {@link Ordered#LOWEST_PRECEDENCE} used as the default order value.</li>
     * </ul>
     * @see #resolveBean()
     */
    @Override
    public int getOrder() {
        if (this.order == null) {
            Object resolvedBean = resolveBean();
            if (resolvedBean instanceof Ordered) {
                this.order = ((Ordered) resolvedBean).getOrder();
            } else 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.
     * <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 {@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, 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.
            this.resolvedBean = obtainBeanFactory().getBean((String) this.beanOrName);
        }
        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) {
        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 {@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) {
        List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
        for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)) {
            ControllerAdvice controllerAdvice = context.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, context, 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();
    }

}