001/* 002 * Copyright 2002-2016 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.lang.annotation.Annotation; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Collections; 023import java.util.LinkedHashSet; 024import java.util.List; 025import java.util.Set; 026 027import org.springframework.beans.factory.BeanFactory; 028import org.springframework.beans.factory.BeanFactoryUtils; 029import org.springframework.context.ApplicationContext; 030import org.springframework.core.Ordered; 031import org.springframework.core.annotation.AnnotatedElementUtils; 032import org.springframework.core.annotation.AnnotationUtils; 033import org.springframework.core.annotation.OrderUtils; 034import org.springframework.util.Assert; 035import org.springframework.util.ClassUtils; 036import org.springframework.util.StringUtils; 037import org.springframework.web.bind.annotation.ControllerAdvice; 038 039/** 040 * Encapsulates information about an {@linkplain ControllerAdvice @ControllerAdvice} 041 * Spring-managed bean without necessarily requiring it to be instantiated. 042 * 043 * <p>The {@link #findAnnotatedBeans(ApplicationContext)} method can be used to 044 * discover such beans. However, a {@code ControllerAdviceBean} may be created 045 * from any object, including ones without an {@code @ControllerAdvice}. 046 * 047 * @author Rossen Stoyanchev 048 * @author Brian Clozel 049 * @author Juergen Hoeller 050 * @since 3.2 051 */ 052public class ControllerAdviceBean implements Ordered { 053 054 private final Object bean; 055 056 private final BeanFactory beanFactory; 057 058 private final int order; 059 060 private final Set<String> basePackages; 061 062 private final List<Class<?>> assignableTypes; 063 064 private final List<Class<? extends Annotation>> annotations; 065 066 067 /** 068 * Create a {@code ControllerAdviceBean} using the given bean instance. 069 * @param bean the bean instance 070 */ 071 public ControllerAdviceBean(Object bean) { 072 this(bean, null); 073 } 074 075 /** 076 * Create a {@code ControllerAdviceBean} using the given bean name. 077 * @param beanName the name of the bean 078 * @param beanFactory a BeanFactory that can be used later to resolve the bean 079 */ 080 public ControllerAdviceBean(String beanName, BeanFactory beanFactory) { 081 this((Object) beanName, beanFactory); 082 } 083 084 private ControllerAdviceBean(Object bean, BeanFactory beanFactory) { 085 this.bean = bean; 086 this.beanFactory = beanFactory; 087 Class<?> beanType; 088 089 if (bean instanceof String) { 090 String beanName = (String) bean; 091 Assert.hasText(beanName, "Bean name must not be null"); 092 Assert.notNull(beanFactory, "BeanFactory must not be null"); 093 if (!beanFactory.containsBean(beanName)) { 094 throw new IllegalArgumentException("BeanFactory [" + beanFactory + 095 "] does not contain specified controller advice bean '" + beanName + "'"); 096 } 097 beanType = this.beanFactory.getType(beanName); 098 this.order = initOrderFromBeanType(beanType); 099 } 100 else { 101 Assert.notNull(bean, "Bean must not be null"); 102 beanType = bean.getClass(); 103 this.order = initOrderFromBean(bean); 104 } 105 106 ControllerAdvice annotation = 107 AnnotatedElementUtils.findMergedAnnotation(beanType, ControllerAdvice.class); 108 109 if (annotation != null) { 110 this.basePackages = initBasePackages(annotation); 111 this.assignableTypes = Arrays.asList(annotation.assignableTypes()); 112 this.annotations = Arrays.asList(annotation.annotations()); 113 } 114 else { 115 this.basePackages = Collections.emptySet(); 116 this.assignableTypes = Collections.emptyList(); 117 this.annotations = Collections.emptyList(); 118 } 119 } 120 121 122 /** 123 * Returns the order value extracted from the {@link ControllerAdvice} 124 * annotation, or {@link Ordered#LOWEST_PRECEDENCE} otherwise. 125 */ 126 @Override 127 public int getOrder() { 128 return this.order; 129 } 130 131 /** 132 * Return the type of the contained bean. 133 * <p>If the bean type is a CGLIB-generated class, the original 134 * user-defined class is returned. 135 */ 136 public Class<?> getBeanType() { 137 Class<?> clazz = (this.bean instanceof String ? 138 this.beanFactory.getType((String) this.bean) : this.bean.getClass()); 139 return ClassUtils.getUserClass(clazz); 140 } 141 142 /** 143 * Return a bean instance if necessary resolving the bean name through the BeanFactory. 144 */ 145 public Object resolveBean() { 146 return (this.bean instanceof String ? this.beanFactory.getBean((String) this.bean) : this.bean); 147 } 148 149 /** 150 * Check whether the given bean type should be assisted by this 151 * {@code @ControllerAdvice} instance. 152 * @param beanType the type of the bean to check 153 * @see org.springframework.web.bind.annotation.ControllerAdvice 154 * @since 4.0 155 */ 156 public boolean isApplicableToBeanType(Class<?> beanType) { 157 if (!hasSelectors()) { 158 return true; 159 } 160 else if (beanType != null) { 161 for (String basePackage : this.basePackages) { 162 if (beanType.getName().startsWith(basePackage)) { 163 return true; 164 } 165 } 166 for (Class<?> clazz : this.assignableTypes) { 167 if (ClassUtils.isAssignable(clazz, beanType)) { 168 return true; 169 } 170 } 171 for (Class<? extends Annotation> annotationClass : this.annotations) { 172 if (AnnotationUtils.findAnnotation(beanType, annotationClass) != null) { 173 return true; 174 } 175 } 176 } 177 return false; 178 } 179 180 private boolean hasSelectors() { 181 return (!this.basePackages.isEmpty() || !this.assignableTypes.isEmpty() || !this.annotations.isEmpty()); 182 } 183 184 185 @Override 186 public boolean equals(Object other) { 187 if (this == other) { 188 return true; 189 } 190 if (!(other instanceof ControllerAdviceBean)) { 191 return false; 192 } 193 ControllerAdviceBean otherAdvice = (ControllerAdviceBean) other; 194 return (this.bean.equals(otherAdvice.bean) && this.beanFactory == otherAdvice.beanFactory); 195 } 196 197 @Override 198 public int hashCode() { 199 return this.bean.hashCode(); 200 } 201 202 @Override 203 public String toString() { 204 return this.bean.toString(); 205 } 206 207 208 /** 209 * Find the names of beans annotated with 210 * {@linkplain ControllerAdvice @ControllerAdvice} in the given 211 * ApplicationContext and wrap them as {@code ControllerAdviceBean} instances. 212 */ 213 public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext applicationContext) { 214 List<ControllerAdviceBean> beans = new ArrayList<ControllerAdviceBean>(); 215 for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class)) { 216 if (applicationContext.findAnnotationOnBean(name, ControllerAdvice.class) != null) { 217 beans.add(new ControllerAdviceBean(name, applicationContext)); 218 } 219 } 220 return beans; 221 } 222 223 private static int initOrderFromBean(Object bean) { 224 return (bean instanceof Ordered ? ((Ordered) bean).getOrder() : initOrderFromBeanType(bean.getClass())); 225 } 226 227 private static int initOrderFromBeanType(Class<?> beanType) { 228 return OrderUtils.getOrder(beanType, Ordered.LOWEST_PRECEDENCE); 229 } 230 231 private static Set<String> initBasePackages(ControllerAdvice annotation) { 232 Set<String> basePackages = new LinkedHashSet<String>(); 233 for (String basePackage : annotation.basePackages()) { 234 if (StringUtils.hasText(basePackage)) { 235 basePackages.add(adaptBasePackage(basePackage)); 236 } 237 } 238 for (Class<?> markerClass : annotation.basePackageClasses()) { 239 basePackages.add(adaptBasePackage(ClassUtils.getPackageName(markerClass))); 240 } 241 return basePackages; 242 } 243 244 private static String adaptBasePackage(String basePackage) { 245 return (basePackage.endsWith(".") ? basePackage : basePackage + "."); 246 } 247 248}