001/* 002 * Copyright 2002-2017 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.context.annotation; 018 019import java.io.IOException; 020import java.lang.annotation.Annotation; 021import java.util.LinkedHashSet; 022import java.util.LinkedList; 023import java.util.List; 024import java.util.Set; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028 029import org.springframework.beans.factory.BeanDefinitionStoreException; 030import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; 031import org.springframework.beans.factory.annotation.Lookup; 032import org.springframework.beans.factory.config.BeanDefinition; 033import org.springframework.beans.factory.support.BeanDefinitionRegistry; 034import org.springframework.context.ResourceLoaderAware; 035import org.springframework.core.env.Environment; 036import org.springframework.core.env.EnvironmentCapable; 037import org.springframework.core.env.StandardEnvironment; 038import org.springframework.core.io.Resource; 039import org.springframework.core.io.ResourceLoader; 040import org.springframework.core.io.support.ResourcePatternResolver; 041import org.springframework.core.io.support.ResourcePatternUtils; 042import org.springframework.core.type.AnnotationMetadata; 043import org.springframework.core.type.classreading.CachingMetadataReaderFactory; 044import org.springframework.core.type.classreading.MetadataReader; 045import org.springframework.core.type.classreading.MetadataReaderFactory; 046import org.springframework.core.type.filter.AnnotationTypeFilter; 047import org.springframework.core.type.filter.TypeFilter; 048import org.springframework.stereotype.Component; 049import org.springframework.stereotype.Controller; 050import org.springframework.stereotype.Repository; 051import org.springframework.stereotype.Service; 052import org.springframework.util.Assert; 053import org.springframework.util.ClassUtils; 054 055/** 056 * A component provider that scans the classpath from a base package. It then 057 * applies exclude and include filters to the resulting classes to find candidates. 058 * 059 * <p>This implementation is based on Spring's 060 * {@link org.springframework.core.type.classreading.MetadataReader MetadataReader} 061 * facility, backed by an ASM {@link org.springframework.asm.ClassReader ClassReader}. 062 * 063 * @author Mark Fisher 064 * @author Juergen Hoeller 065 * @author Ramnivas Laddad 066 * @author Chris Beams 067 * @since 2.5 068 * @see org.springframework.core.type.classreading.MetadataReaderFactory 069 * @see org.springframework.core.type.AnnotationMetadata 070 * @see ScannedGenericBeanDefinition 071 */ 072public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware { 073 074 static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; 075 076 077 protected final Log logger = LogFactory.getLog(getClass()); 078 079 private String resourcePattern = DEFAULT_RESOURCE_PATTERN; 080 081 private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>(); 082 083 private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>(); 084 085 private Environment environment; 086 087 private ConditionEvaluator conditionEvaluator; 088 089 private ResourcePatternResolver resourcePatternResolver; 090 091 private MetadataReaderFactory metadataReaderFactory; 092 093 094 /** 095 * Protected constructor for flexible subclass initialization. 096 * @since 4.3.6 097 */ 098 protected ClassPathScanningCandidateComponentProvider() { 099 } 100 101 /** 102 * Create a ClassPathScanningCandidateComponentProvider with a {@link StandardEnvironment}. 103 * @param useDefaultFilters whether to register the default filters for the 104 * {@link Component @Component}, {@link Repository @Repository}, 105 * {@link Service @Service}, and {@link Controller @Controller} 106 * stereotype annotations 107 * @see #registerDefaultFilters() 108 */ 109 public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) { 110 this(useDefaultFilters, new StandardEnvironment()); 111 } 112 113 /** 114 * Create a ClassPathScanningCandidateComponentProvider with the given {@link Environment}. 115 * @param useDefaultFilters whether to register the default filters for the 116 * {@link Component @Component}, {@link Repository @Repository}, 117 * {@link Service @Service}, and {@link Controller @Controller} 118 * stereotype annotations 119 * @param environment the Environment to use 120 * @see #registerDefaultFilters() 121 */ 122 public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) { 123 if (useDefaultFilters) { 124 registerDefaultFilters(); 125 } 126 setEnvironment(environment); 127 setResourceLoader(null); 128 } 129 130 131 /** 132 * Set the resource pattern to use when scanning the classpath. 133 * This value will be appended to each base package name. 134 * @see #findCandidateComponents(String) 135 * @see #DEFAULT_RESOURCE_PATTERN 136 */ 137 public void setResourcePattern(String resourcePattern) { 138 Assert.notNull(resourcePattern, "'resourcePattern' must not be null"); 139 this.resourcePattern = resourcePattern; 140 } 141 142 /** 143 * Add an include type filter to the <i>end</i> of the inclusion list. 144 */ 145 public void addIncludeFilter(TypeFilter includeFilter) { 146 this.includeFilters.add(includeFilter); 147 } 148 149 /** 150 * Add an exclude type filter to the <i>front</i> of the exclusion list. 151 */ 152 public void addExcludeFilter(TypeFilter excludeFilter) { 153 this.excludeFilters.add(0, excludeFilter); 154 } 155 156 /** 157 * Reset the configured type filters. 158 * @param useDefaultFilters whether to re-register the default filters for 159 * the {@link Component @Component}, {@link Repository @Repository}, 160 * {@link Service @Service}, and {@link Controller @Controller} 161 * stereotype annotations 162 * @see #registerDefaultFilters() 163 */ 164 public void resetFilters(boolean useDefaultFilters) { 165 this.includeFilters.clear(); 166 this.excludeFilters.clear(); 167 if (useDefaultFilters) { 168 registerDefaultFilters(); 169 } 170 } 171 172 /** 173 * Register the default filter for {@link Component @Component}. 174 * <p>This will implicitly register all annotations that have the 175 * {@link Component @Component} meta-annotation including the 176 * {@link Repository @Repository}, {@link Service @Service}, and 177 * {@link Controller @Controller} stereotype annotations. 178 * <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and 179 * JSR-330's {@link javax.inject.Named} annotations, if available. 180 * 181 */ 182 @SuppressWarnings("unchecked") 183 protected void registerDefaultFilters() { 184 this.includeFilters.add(new AnnotationTypeFilter(Component.class)); 185 ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); 186 try { 187 this.includeFilters.add(new AnnotationTypeFilter( 188 ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); 189 logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); 190 } 191 catch (ClassNotFoundException ex) { 192 // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip. 193 } 194 try { 195 this.includeFilters.add(new AnnotationTypeFilter( 196 ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false)); 197 logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); 198 } 199 catch (ClassNotFoundException ex) { 200 // JSR-330 API not available - simply skip. 201 } 202 } 203 204 /** 205 * Set the Environment to use when resolving placeholders and evaluating 206 * {@link Conditional @Conditional}-annotated component classes. 207 * <p>The default is a {@link StandardEnvironment}. 208 * @param environment the Environment to use 209 */ 210 public void setEnvironment(Environment environment) { 211 Assert.notNull(environment, "Environment must not be null"); 212 this.environment = environment; 213 this.conditionEvaluator = null; 214 } 215 216 @Override 217 public final Environment getEnvironment() { 218 return this.environment; 219 } 220 221 /** 222 * Return the {@link BeanDefinitionRegistry} used by this scanner, if any. 223 */ 224 protected BeanDefinitionRegistry getRegistry() { 225 return null; 226 } 227 228 /** 229 * Set the {@link ResourceLoader} to use for resource locations. 230 * This will typically be a {@link ResourcePatternResolver} implementation. 231 * <p>Default is a {@code PathMatchingResourcePatternResolver}, also capable of 232 * resource pattern resolving through the {@code ResourcePatternResolver} interface. 233 * @see org.springframework.core.io.support.ResourcePatternResolver 234 * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver 235 */ 236 @Override 237 public void setResourceLoader(ResourceLoader resourceLoader) { 238 this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); 239 this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); 240 } 241 242 /** 243 * Return the ResourceLoader that this component provider uses. 244 */ 245 public final ResourceLoader getResourceLoader() { 246 return this.resourcePatternResolver; 247 } 248 249 /** 250 * Set the {@link MetadataReaderFactory} to use. 251 * <p>Default is a {@link CachingMetadataReaderFactory} for the specified 252 * {@linkplain #setResourceLoader resource loader}. 253 * <p>Call this setter method <i>after</i> {@link #setResourceLoader} in order 254 * for the given MetadataReaderFactory to override the default factory. 255 */ 256 public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) { 257 this.metadataReaderFactory = metadataReaderFactory; 258 } 259 260 /** 261 * Return the MetadataReaderFactory used by this component provider. 262 */ 263 public final MetadataReaderFactory getMetadataReaderFactory() { 264 return this.metadataReaderFactory; 265 } 266 267 268 /** 269 * Scan the class path for candidate components. 270 * @param basePackage the package to check for annotated classes 271 * @return a corresponding Set of autodetected bean definitions 272 */ 273 public Set<BeanDefinition> findCandidateComponents(String basePackage) { 274 Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>(); 275 try { 276 String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + 277 resolveBasePackage(basePackage) + '/' + this.resourcePattern; 278 Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); 279 boolean traceEnabled = logger.isTraceEnabled(); 280 boolean debugEnabled = logger.isDebugEnabled(); 281 for (Resource resource : resources) { 282 if (traceEnabled) { 283 logger.trace("Scanning " + resource); 284 } 285 if (resource.isReadable()) { 286 try { 287 MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); 288 if (isCandidateComponent(metadataReader)) { 289 ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); 290 sbd.setResource(resource); 291 sbd.setSource(resource); 292 if (isCandidateComponent(sbd)) { 293 if (debugEnabled) { 294 logger.debug("Identified candidate component class: " + resource); 295 } 296 candidates.add(sbd); 297 } 298 else { 299 if (debugEnabled) { 300 logger.debug("Ignored because not a concrete top-level class: " + resource); 301 } 302 } 303 } 304 else { 305 if (traceEnabled) { 306 logger.trace("Ignored because not matching any filter: " + resource); 307 } 308 } 309 } 310 catch (Throwable ex) { 311 throw new BeanDefinitionStoreException( 312 "Failed to read candidate component class: " + resource, ex); 313 } 314 } 315 else { 316 if (traceEnabled) { 317 logger.trace("Ignored because not readable: " + resource); 318 } 319 } 320 } 321 } 322 catch (IOException ex) { 323 throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); 324 } 325 return candidates; 326 } 327 328 329 /** 330 * Resolve the specified base package into a pattern specification for 331 * the package search path. 332 * <p>The default implementation resolves placeholders against system properties, 333 * and converts a "."-based package path to a "/"-based resource path. 334 * @param basePackage the base package as specified by the user 335 * @return the pattern specification to be used for package searching 336 */ 337 protected String resolveBasePackage(String basePackage) { 338 return ClassUtils.convertClassNameToResourcePath(this.environment.resolveRequiredPlaceholders(basePackage)); 339 } 340 341 /** 342 * Determine whether the given class does not match any exclude filter 343 * and does match at least one include filter. 344 * @param metadataReader the ASM ClassReader for the class 345 * @return whether the class qualifies as a candidate component 346 */ 347 protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { 348 for (TypeFilter tf : this.excludeFilters) { 349 if (tf.match(metadataReader, this.metadataReaderFactory)) { 350 return false; 351 } 352 } 353 for (TypeFilter tf : this.includeFilters) { 354 if (tf.match(metadataReader, this.metadataReaderFactory)) { 355 return isConditionMatch(metadataReader); 356 } 357 } 358 return false; 359 } 360 361 /** 362 * Determine whether the given class is a candidate component based on any 363 * {@code @Conditional} annotations. 364 * @param metadataReader the ASM ClassReader for the class 365 * @return whether the class qualifies as a candidate component 366 */ 367 private boolean isConditionMatch(MetadataReader metadataReader) { 368 if (this.conditionEvaluator == null) { 369 this.conditionEvaluator = new ConditionEvaluator(getRegistry(), getEnvironment(), getResourceLoader()); 370 } 371 return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata()); 372 } 373 374 /** 375 * Determine whether the given bean definition qualifies as candidate. 376 * <p>The default implementation checks whether the class is not an interface 377 * and not dependent on an enclosing class. 378 * <p>Can be overridden in subclasses. 379 * @param beanDefinition the bean definition to check 380 * @return whether the bean definition qualifies as a candidate component 381 */ 382 protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { 383 AnnotationMetadata metadata = beanDefinition.getMetadata(); 384 return (metadata.isIndependent() && (metadata.isConcrete() || 385 (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())))); 386 } 387 388 389 /** 390 * Clear the underlying metadata cache, removing all cached class metadata. 391 */ 392 public void clearCache() { 393 if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { 394 ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); 395 } 396 } 397 398}