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 — for example, as a request-scoped or session-scoped 135 * bean — 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}