001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.method;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import org.springframework.aop.scope.ScopedProxyUtils;
023import org.springframework.beans.factory.BeanFactory;
024import org.springframework.beans.factory.BeanFactoryUtils;
025import org.springframework.context.ApplicationContext;
026import org.springframework.core.OrderComparator;
027import org.springframework.core.Ordered;
028import org.springframework.core.annotation.AnnotatedElementUtils;
029import org.springframework.core.annotation.OrderUtils;
030import org.springframework.lang.Nullable;
031import org.springframework.util.Assert;
032import org.springframework.util.ClassUtils;
033import org.springframework.web.bind.annotation.ControllerAdvice;
034
035/**
036 * Encapsulates information about an {@link ControllerAdvice @ControllerAdvice}
037 * Spring-managed bean without necessarily requiring it to be instantiated.
038 *
039 * <p>The {@link #findAnnotatedBeans(ApplicationContext)} method can be used to
040 * discover such beans. However, a {@code ControllerAdviceBean} may be created
041 * from any object, including ones without an {@code @ControllerAdvice} annotation.
042 *
043 * @author Rossen Stoyanchev
044 * @author Brian Clozel
045 * @author Juergen Hoeller
046 * @author Sam Brannen
047 * @since 3.2
048 */
049public class ControllerAdviceBean implements Ordered {
050
051        /**
052         * Reference to the actual bean instance or a {@code String} representing
053         * the bean name.
054         */
055        private final Object beanOrName;
056
057        private final boolean isSingleton;
058
059        /**
060         * Reference to the resolved bean instance, potentially lazily retrieved
061         * via the {@code BeanFactory}.
062         */
063        @Nullable
064        private Object resolvedBean;
065
066        @Nullable
067        private final Class<?> beanType;
068
069        private final HandlerTypePredicate beanTypePredicate;
070
071        @Nullable
072        private final BeanFactory beanFactory;
073
074        @Nullable
075        private Integer order;
076
077
078        /**
079         * Create a {@code ControllerAdviceBean} using the given bean instance.
080         * @param bean the bean instance
081         */
082        public ControllerAdviceBean(Object bean) {
083                Assert.notNull(bean, "Bean must not be null");
084                this.beanOrName = bean;
085                this.isSingleton = true;
086                this.resolvedBean = bean;
087                this.beanType = ClassUtils.getUserClass(bean.getClass());
088                this.beanTypePredicate = createBeanTypePredicate(this.beanType);
089                this.beanFactory = null;
090        }
091
092        /**
093         * Create a {@code ControllerAdviceBean} using the given bean name and
094         * {@code BeanFactory}.
095         * @param beanName the name of the bean
096         * @param beanFactory a {@code BeanFactory} to retrieve the bean type initially
097         * and later to resolve the actual bean
098         */
099        public ControllerAdviceBean(String beanName, BeanFactory beanFactory) {
100                this(beanName, beanFactory, null);
101        }
102
103        /**
104         * Create a {@code ControllerAdviceBean} using the given bean name,
105         * {@code BeanFactory}, and {@link ControllerAdvice @ControllerAdvice}
106         * annotation.
107         * @param beanName the name of the bean
108         * @param beanFactory a {@code BeanFactory} to retrieve the bean type initially
109         * and later to resolve the actual bean
110         * @param controllerAdvice the {@code @ControllerAdvice} annotation for the
111         * bean, or {@code null} if not yet retrieved
112         * @since 5.2
113         */
114        public ControllerAdviceBean(String beanName, BeanFactory beanFactory, @Nullable ControllerAdvice controllerAdvice) {
115                Assert.hasText(beanName, "Bean name must contain text");
116                Assert.notNull(beanFactory, "BeanFactory must not be null");
117                Assert.isTrue(beanFactory.containsBean(beanName), () -> "BeanFactory [" + beanFactory +
118                                "] does not contain specified controller advice bean '" + beanName + "'");
119
120                this.beanOrName = beanName;
121                this.isSingleton = beanFactory.isSingleton(beanName);
122                this.beanType = getBeanType(beanName, beanFactory);
123                this.beanTypePredicate = (controllerAdvice != null ? createBeanTypePredicate(controllerAdvice) :
124                                createBeanTypePredicate(this.beanType));
125                this.beanFactory = beanFactory;
126        }
127
128
129        /**
130         * Get the order value for the contained bean.
131         * <p>As of Spring Framework 5.2, the order value is lazily retrieved using
132         * the following algorithm and cached. Note, however, that a
133         * {@link ControllerAdvice @ControllerAdvice} bean that is configured as a
134         * scoped bean &mdash; for example, as a request-scoped or session-scoped
135         * bean &mdash; will not be eagerly resolved. Consequently, {@link Ordered} is
136         * not honored for scoped {@code @ControllerAdvice} beans.
137         * <ul>
138         * <li>If the {@linkplain #resolveBean resolved bean} implements {@link Ordered},
139         * use the value returned by {@link Ordered#getOrder()}.</li>
140         * <li>If the {@linkplain #getBeanType() bean type} is known, use the value returned
141         * by {@link OrderUtils#getOrder(Class, int)} with {@link Ordered#LOWEST_PRECEDENCE}
142         * used as the default order value.</li>
143         * <li>Otherwise use {@link Ordered#LOWEST_PRECEDENCE} as the default, fallback
144         * order value.</li>
145         * </ul>
146         * @see #resolveBean()
147         */
148        @Override
149        public int getOrder() {
150                if (this.order == null) {
151                        Object resolvedBean = null;
152                        if (this.beanFactory != null && this.beanOrName instanceof String) {
153                                String beanName = (String) this.beanOrName;
154                                String targetBeanName = ScopedProxyUtils.getTargetBeanName(beanName);
155                                boolean isScopedProxy = this.beanFactory.containsBean(targetBeanName);
156                                // Avoid eager @ControllerAdvice bean resolution for scoped proxies,
157                                // since attempting to do so during context initialization would result
158                                // in an exception due to the current absence of the scope. For example,
159                                // an HTTP request or session scope is not active during initialization.
160                                if (!isScopedProxy && !ScopedProxyUtils.isScopedTarget(beanName)) {
161                                        resolvedBean = resolveBean();
162                                }
163                        }
164                        else {
165                                resolvedBean = resolveBean();
166                        }
167
168                        if (resolvedBean instanceof Ordered) {
169                                this.order = ((Ordered) resolvedBean).getOrder();
170                        }
171                        else if (this.beanType != null) {
172                                this.order = OrderUtils.getOrder(this.beanType, Ordered.LOWEST_PRECEDENCE);
173                        }
174                        else {
175                                this.order = Ordered.LOWEST_PRECEDENCE;
176                        }
177                }
178                return this.order;
179        }
180
181        /**
182         * Return the type of the contained bean.
183         * <p>If the bean type is a CGLIB-generated class, the original user-defined
184         * class is returned.
185         */
186        @Nullable
187        public Class<?> getBeanType() {
188                return this.beanType;
189        }
190
191        /**
192         * Get the bean instance for this {@code ControllerAdviceBean}, if necessary
193         * resolving the bean name through the {@link BeanFactory}.
194         * <p>As of Spring Framework 5.2, once the bean instance has been resolved it
195         * will be cached if it is a singleton, thereby avoiding repeated lookups in
196         * the {@code BeanFactory}.
197         */
198        public Object resolveBean() {
199                if (this.resolvedBean == null) {
200                        // this.beanOrName must be a String representing the bean name if
201                        // this.resolvedBean is null.
202                        Object resolvedBean = obtainBeanFactory().getBean((String) this.beanOrName);
203                        // Don't cache non-singletons (e.g., prototypes).
204                        if (!this.isSingleton) {
205                                return resolvedBean;
206                        }
207                        this.resolvedBean = resolvedBean;
208                }
209                return this.resolvedBean;
210        }
211
212        private BeanFactory obtainBeanFactory() {
213                Assert.state(this.beanFactory != null, "No BeanFactory set");
214                return this.beanFactory;
215        }
216
217        /**
218         * Check whether the given bean type should be advised by this
219         * {@code ControllerAdviceBean}.
220         * @param beanType the type of the bean to check
221         * @since 4.0
222         * @see ControllerAdvice
223         */
224        public boolean isApplicableToBeanType(@Nullable Class<?> beanType) {
225                return this.beanTypePredicate.test(beanType);
226        }
227
228
229        @Override
230        public boolean equals(@Nullable Object other) {
231                if (this == other) {
232                        return true;
233                }
234                if (!(other instanceof ControllerAdviceBean)) {
235                        return false;
236                }
237                ControllerAdviceBean otherAdvice = (ControllerAdviceBean) other;
238                return (this.beanOrName.equals(otherAdvice.beanOrName) && this.beanFactory == otherAdvice.beanFactory);
239        }
240
241        @Override
242        public int hashCode() {
243                return this.beanOrName.hashCode();
244        }
245
246        @Override
247        public String toString() {
248                return this.beanOrName.toString();
249        }
250
251
252        /**
253         * Find beans annotated with {@link ControllerAdvice @ControllerAdvice} in the
254         * given {@link ApplicationContext} and wrap them as {@code ControllerAdviceBean}
255         * instances.
256         * <p>As of Spring Framework 5.2, the {@code ControllerAdviceBean} instances
257         * in the returned list are sorted using {@link OrderComparator#sort(List)}.
258         * @see #getOrder()
259         * @see OrderComparator
260         * @see Ordered
261         */
262        public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
263                List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
264                for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)) {
265                        if (!ScopedProxyUtils.isScopedTarget(name)) {
266                                ControllerAdvice controllerAdvice = context.findAnnotationOnBean(name, ControllerAdvice.class);
267                                if (controllerAdvice != null) {
268                                        // Use the @ControllerAdvice annotation found by findAnnotationOnBean()
269                                        // in order to avoid a subsequent lookup of the same annotation.
270                                        adviceBeans.add(new ControllerAdviceBean(name, context, controllerAdvice));
271                                }
272                        }
273                }
274                OrderComparator.sort(adviceBeans);
275                return adviceBeans;
276        }
277
278        @Nullable
279        private static Class<?> getBeanType(String beanName, BeanFactory beanFactory) {
280                Class<?> beanType = beanFactory.getType(beanName);
281                return (beanType != null ? ClassUtils.getUserClass(beanType) : null);
282        }
283
284        private static HandlerTypePredicate createBeanTypePredicate(@Nullable Class<?> beanType) {
285                ControllerAdvice controllerAdvice = (beanType != null ?
286                                AnnotatedElementUtils.findMergedAnnotation(beanType, ControllerAdvice.class) : null);
287                return createBeanTypePredicate(controllerAdvice);
288        }
289
290        private static HandlerTypePredicate createBeanTypePredicate(@Nullable ControllerAdvice controllerAdvice) {
291                if (controllerAdvice != null) {
292                        return HandlerTypePredicate.builder()
293                                        .basePackage(controllerAdvice.basePackages())
294                                        .basePackageClass(controllerAdvice.basePackageClasses())
295                                        .assignableType(controllerAdvice.assignableTypes())
296                                        .annotation(controllerAdvice.annotations())
297                                        .build();
298                }
299                return HandlerTypePredicate.forAnyHandlerType();
300        }
301
302}