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.context.annotation; 018 019import java.io.IOException; 020import java.lang.annotation.Annotation; 021import java.util.HashSet; 022import java.util.LinkedHashSet; 023import java.util.LinkedList; 024import java.util.List; 025import java.util.Set; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030import org.springframework.beans.factory.BeanDefinitionStoreException; 031import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; 032import org.springframework.beans.factory.annotation.Lookup; 033import org.springframework.beans.factory.config.BeanDefinition; 034import org.springframework.beans.factory.support.BeanDefinitionRegistry; 035import org.springframework.context.ResourceLoaderAware; 036import org.springframework.context.index.CandidateComponentsIndex; 037import org.springframework.context.index.CandidateComponentsIndexLoader; 038import org.springframework.core.annotation.AnnotationUtils; 039import org.springframework.core.env.Environment; 040import org.springframework.core.env.EnvironmentCapable; 041import org.springframework.core.env.StandardEnvironment; 042import org.springframework.core.io.Resource; 043import org.springframework.core.io.ResourceLoader; 044import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 045import org.springframework.core.io.support.ResourcePatternResolver; 046import org.springframework.core.io.support.ResourcePatternUtils; 047import org.springframework.core.type.AnnotationMetadata; 048import org.springframework.core.type.classreading.CachingMetadataReaderFactory; 049import org.springframework.core.type.classreading.MetadataReader; 050import org.springframework.core.type.classreading.MetadataReaderFactory; 051import org.springframework.core.type.filter.AnnotationTypeFilter; 052import org.springframework.core.type.filter.AssignableTypeFilter; 053import org.springframework.core.type.filter.TypeFilter; 054import org.springframework.lang.Nullable; 055import org.springframework.stereotype.Component; 056import org.springframework.stereotype.Controller; 057import org.springframework.stereotype.Indexed; 058import org.springframework.stereotype.Repository; 059import org.springframework.stereotype.Service; 060import org.springframework.util.Assert; 061import org.springframework.util.ClassUtils; 062 063/** 064 * A component provider that provides candidate components from a base package. Can 065 * use {@link CandidateComponentsIndex the index} if it is available of scans the 066 * classpath otherwise. Candidate components are identified by applying exclude and 067 * include filters. {@link AnnotationTypeFilter}, {@link AssignableTypeFilter} include 068 * filters on an annotation/superclass that are annotated with {@link Indexed} are 069 * supported: if any other include filter is specified, the index is ignored and 070 * classpath scanning is used instead. 071 * 072 * <p>This implementation is based on Spring's 073 * {@link org.springframework.core.type.classreading.MetadataReader MetadataReader} 074 * facility, backed by an ASM {@link org.springframework.asm.ClassReader ClassReader}. 075 * 076 * @author Mark Fisher 077 * @author Juergen Hoeller 078 * @author Ramnivas Laddad 079 * @author Chris Beams 080 * @author Stephane Nicoll 081 * @since 2.5 082 * @see org.springframework.core.type.classreading.MetadataReaderFactory 083 * @see org.springframework.core.type.AnnotationMetadata 084 * @see ScannedGenericBeanDefinition 085 * @see CandidateComponentsIndex 086 */ 087public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware { 088 089 static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; 090 091 092 protected final Log logger = LogFactory.getLog(getClass()); 093 094 private String resourcePattern = DEFAULT_RESOURCE_PATTERN; 095 096 private final List<TypeFilter> includeFilters = new LinkedList<>(); 097 098 private final List<TypeFilter> excludeFilters = new LinkedList<>(); 099 100 @Nullable 101 private Environment environment; 102 103 @Nullable 104 private ConditionEvaluator conditionEvaluator; 105 106 @Nullable 107 private ResourcePatternResolver resourcePatternResolver; 108 109 @Nullable 110 private MetadataReaderFactory metadataReaderFactory; 111 112 @Nullable 113 private CandidateComponentsIndex componentsIndex; 114 115 116 /** 117 * Protected constructor for flexible subclass initialization. 118 * @since 4.3.6 119 */ 120 protected ClassPathScanningCandidateComponentProvider() { 121 } 122 123 /** 124 * Create a ClassPathScanningCandidateComponentProvider with a {@link StandardEnvironment}. 125 * @param useDefaultFilters whether to register the default filters for the 126 * {@link Component @Component}, {@link Repository @Repository}, 127 * {@link Service @Service}, and {@link Controller @Controller} 128 * stereotype annotations 129 * @see #registerDefaultFilters() 130 */ 131 public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) { 132 this(useDefaultFilters, new StandardEnvironment()); 133 } 134 135 /** 136 * Create a ClassPathScanningCandidateComponentProvider with the given {@link Environment}. 137 * @param useDefaultFilters whether to register the default filters for the 138 * {@link Component @Component}, {@link Repository @Repository}, 139 * {@link Service @Service}, and {@link Controller @Controller} 140 * stereotype annotations 141 * @param environment the Environment to use 142 * @see #registerDefaultFilters() 143 */ 144 public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) { 145 if (useDefaultFilters) { 146 registerDefaultFilters(); 147 } 148 setEnvironment(environment); 149 setResourceLoader(null); 150 } 151 152 153 /** 154 * Set the resource pattern to use when scanning the classpath. 155 * This value will be appended to each base package name. 156 * @see #findCandidateComponents(String) 157 * @see #DEFAULT_RESOURCE_PATTERN 158 */ 159 public void setResourcePattern(String resourcePattern) { 160 Assert.notNull(resourcePattern, "'resourcePattern' must not be null"); 161 this.resourcePattern = resourcePattern; 162 } 163 164 /** 165 * Add an include type filter to the <i>end</i> of the inclusion list. 166 */ 167 public void addIncludeFilter(TypeFilter includeFilter) { 168 this.includeFilters.add(includeFilter); 169 } 170 171 /** 172 * Add an exclude type filter to the <i>front</i> of the exclusion list. 173 */ 174 public void addExcludeFilter(TypeFilter excludeFilter) { 175 this.excludeFilters.add(0, excludeFilter); 176 } 177 178 /** 179 * Reset the configured type filters. 180 * @param useDefaultFilters whether to re-register the default filters for 181 * the {@link Component @Component}, {@link Repository @Repository}, 182 * {@link Service @Service}, and {@link Controller @Controller} 183 * stereotype annotations 184 * @see #registerDefaultFilters() 185 */ 186 public void resetFilters(boolean useDefaultFilters) { 187 this.includeFilters.clear(); 188 this.excludeFilters.clear(); 189 if (useDefaultFilters) { 190 registerDefaultFilters(); 191 } 192 } 193 194 /** 195 * Register the default filter for {@link Component @Component}. 196 * <p>This will implicitly register all annotations that have the 197 * {@link Component @Component} meta-annotation including the 198 * {@link Repository @Repository}, {@link Service @Service}, and 199 * {@link Controller @Controller} stereotype annotations. 200 * <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and 201 * JSR-330's {@link javax.inject.Named} annotations, if available. 202 * 203 */ 204 @SuppressWarnings("unchecked") 205 protected void registerDefaultFilters() { 206 this.includeFilters.add(new AnnotationTypeFilter(Component.class)); 207 ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); 208 try { 209 this.includeFilters.add(new AnnotationTypeFilter( 210 ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); 211 logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); 212 } 213 catch (ClassNotFoundException ex) { 214 // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip. 215 } 216 try { 217 this.includeFilters.add(new AnnotationTypeFilter( 218 ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false)); 219 logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); 220 } 221 catch (ClassNotFoundException ex) { 222 // JSR-330 API not available - simply skip. 223 } 224 } 225 226 /** 227 * Set the Environment to use when resolving placeholders and evaluating 228 * {@link Conditional @Conditional}-annotated component classes. 229 * <p>The default is a {@link StandardEnvironment}. 230 * @param environment the Environment to use 231 */ 232 public void setEnvironment(Environment environment) { 233 Assert.notNull(environment, "Environment must not be null"); 234 this.environment = environment; 235 this.conditionEvaluator = null; 236 } 237 238 @Override 239 public final Environment getEnvironment() { 240 if (this.environment == null) { 241 this.environment = new StandardEnvironment(); 242 } 243 return this.environment; 244 } 245 246 /** 247 * Return the {@link BeanDefinitionRegistry} used by this scanner, if any. 248 */ 249 @Nullable 250 protected BeanDefinitionRegistry getRegistry() { 251 return null; 252 } 253 254 /** 255 * Set the {@link ResourceLoader} to use for resource locations. 256 * This will typically be a {@link ResourcePatternResolver} implementation. 257 * <p>Default is a {@code PathMatchingResourcePatternResolver}, also capable of 258 * resource pattern resolving through the {@code ResourcePatternResolver} interface. 259 * @see org.springframework.core.io.support.ResourcePatternResolver 260 * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver 261 */ 262 @Override 263 public void setResourceLoader(@Nullable ResourceLoader resourceLoader) { 264 this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); 265 this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); 266 this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(this.resourcePatternResolver.getClassLoader()); 267 } 268 269 /** 270 * Return the ResourceLoader that this component provider uses. 271 */ 272 public final ResourceLoader getResourceLoader() { 273 return getResourcePatternResolver(); 274 } 275 276 private ResourcePatternResolver getResourcePatternResolver() { 277 if (this.resourcePatternResolver == null) { 278 this.resourcePatternResolver = new PathMatchingResourcePatternResolver(); 279 } 280 return this.resourcePatternResolver; 281 } 282 283 /** 284 * Set the {@link MetadataReaderFactory} to use. 285 * <p>Default is a {@link CachingMetadataReaderFactory} for the specified 286 * {@linkplain #setResourceLoader resource loader}. 287 * <p>Call this setter method <i>after</i> {@link #setResourceLoader} in order 288 * for the given MetadataReaderFactory to override the default factory. 289 */ 290 public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) { 291 this.metadataReaderFactory = metadataReaderFactory; 292 } 293 294 /** 295 * Return the MetadataReaderFactory used by this component provider. 296 */ 297 public final MetadataReaderFactory getMetadataReaderFactory() { 298 if (this.metadataReaderFactory == null) { 299 this.metadataReaderFactory = new CachingMetadataReaderFactory(); 300 } 301 return this.metadataReaderFactory; 302 } 303 304 305 /** 306 * Scan the class path for candidate components. 307 * @param basePackage the package to check for annotated classes 308 * @return a corresponding Set of autodetected bean definitions 309 */ 310 public Set<BeanDefinition> findCandidateComponents(String basePackage) { 311 if (this.componentsIndex != null && indexSupportsIncludeFilters()) { 312 return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); 313 } 314 else { 315 return scanCandidateComponents(basePackage); 316 } 317 } 318 319 /** 320 * Determine if the index can be used by this instance. 321 * @return {@code true} if the index is available and the configuration of this 322 * instance is supported by it, {@code false} otherwise 323 * @since 5.0 324 */ 325 private boolean indexSupportsIncludeFilters() { 326 for (TypeFilter includeFilter : this.includeFilters) { 327 if (!indexSupportsIncludeFilter(includeFilter)) { 328 return false; 329 } 330 } 331 return true; 332 } 333 334 /** 335 * Determine if the specified include {@link TypeFilter} is supported by the index. 336 * @param filter the filter to check 337 * @return whether the index supports this include filter 338 * @since 5.0 339 * @see #extractStereotype(TypeFilter) 340 */ 341 private boolean indexSupportsIncludeFilter(TypeFilter filter) { 342 if (filter instanceof AnnotationTypeFilter) { 343 Class<? extends Annotation> annotation = ((AnnotationTypeFilter) filter).getAnnotationType(); 344 return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotation) || 345 annotation.getName().startsWith("javax.")); 346 } 347 if (filter instanceof AssignableTypeFilter) { 348 Class<?> target = ((AssignableTypeFilter) filter).getTargetType(); 349 return AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, target); 350 } 351 return false; 352 } 353 354 /** 355 * Extract the stereotype to use for the specified compatible filter. 356 * @param filter the filter to handle 357 * @return the stereotype in the index matching this filter 358 * @since 5.0 359 * @see #indexSupportsIncludeFilter(TypeFilter) 360 */ 361 @Nullable 362 private String extractStereotype(TypeFilter filter) { 363 if (filter instanceof AnnotationTypeFilter) { 364 return ((AnnotationTypeFilter) filter).getAnnotationType().getName(); 365 } 366 if (filter instanceof AssignableTypeFilter) { 367 return ((AssignableTypeFilter) filter).getTargetType().getName(); 368 } 369 return null; 370 } 371 372 private Set<BeanDefinition> addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage) { 373 Set<BeanDefinition> candidates = new LinkedHashSet<>(); 374 try { 375 Set<String> types = new HashSet<>(); 376 for (TypeFilter filter : this.includeFilters) { 377 String stereotype = extractStereotype(filter); 378 if (stereotype == null) { 379 throw new IllegalArgumentException("Failed to extract stereotype from " + filter); 380 } 381 types.addAll(index.getCandidateTypes(basePackage, stereotype)); 382 } 383 boolean traceEnabled = logger.isTraceEnabled(); 384 boolean debugEnabled = logger.isDebugEnabled(); 385 for (String type : types) { 386 MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(type); 387 if (isCandidateComponent(metadataReader)) { 388 ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); 389 sbd.setSource(metadataReader.getResource()); 390 if (isCandidateComponent(sbd)) { 391 if (debugEnabled) { 392 logger.debug("Using candidate component class from index: " + type); 393 } 394 candidates.add(sbd); 395 } 396 else { 397 if (debugEnabled) { 398 logger.debug("Ignored because not a concrete top-level class: " + type); 399 } 400 } 401 } 402 else { 403 if (traceEnabled) { 404 logger.trace("Ignored because matching an exclude filter: " + type); 405 } 406 } 407 } 408 } 409 catch (IOException ex) { 410 throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); 411 } 412 return candidates; 413 } 414 415 private Set<BeanDefinition> scanCandidateComponents(String basePackage) { 416 Set<BeanDefinition> candidates = new LinkedHashSet<>(); 417 try { 418 String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + 419 resolveBasePackage(basePackage) + '/' + this.resourcePattern; 420 Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); 421 boolean traceEnabled = logger.isTraceEnabled(); 422 boolean debugEnabled = logger.isDebugEnabled(); 423 for (Resource resource : resources) { 424 if (traceEnabled) { 425 logger.trace("Scanning " + resource); 426 } 427 if (resource.isReadable()) { 428 try { 429 MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); 430 if (isCandidateComponent(metadataReader)) { 431 ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); 432 sbd.setSource(resource); 433 if (isCandidateComponent(sbd)) { 434 if (debugEnabled) { 435 logger.debug("Identified candidate component class: " + resource); 436 } 437 candidates.add(sbd); 438 } 439 else { 440 if (debugEnabled) { 441 logger.debug("Ignored because not a concrete top-level class: " + resource); 442 } 443 } 444 } 445 else { 446 if (traceEnabled) { 447 logger.trace("Ignored because not matching any filter: " + resource); 448 } 449 } 450 } 451 catch (Throwable ex) { 452 throw new BeanDefinitionStoreException( 453 "Failed to read candidate component class: " + resource, ex); 454 } 455 } 456 else { 457 if (traceEnabled) { 458 logger.trace("Ignored because not readable: " + resource); 459 } 460 } 461 } 462 } 463 catch (IOException ex) { 464 throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); 465 } 466 return candidates; 467 } 468 469 470 /** 471 * Resolve the specified base package into a pattern specification for 472 * the package search path. 473 * <p>The default implementation resolves placeholders against system properties, 474 * and converts a "."-based package path to a "/"-based resource path. 475 * @param basePackage the base package as specified by the user 476 * @return the pattern specification to be used for package searching 477 */ 478 protected String resolveBasePackage(String basePackage) { 479 return ClassUtils.convertClassNameToResourcePath(getEnvironment().resolveRequiredPlaceholders(basePackage)); 480 } 481 482 /** 483 * Determine whether the given class does not match any exclude filter 484 * and does match at least one include filter. 485 * @param metadataReader the ASM ClassReader for the class 486 * @return whether the class qualifies as a candidate component 487 */ 488 protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { 489 for (TypeFilter tf : this.excludeFilters) { 490 if (tf.match(metadataReader, getMetadataReaderFactory())) { 491 return false; 492 } 493 } 494 for (TypeFilter tf : this.includeFilters) { 495 if (tf.match(metadataReader, getMetadataReaderFactory())) { 496 return isConditionMatch(metadataReader); 497 } 498 } 499 return false; 500 } 501 502 /** 503 * Determine whether the given class is a candidate component based on any 504 * {@code @Conditional} annotations. 505 * @param metadataReader the ASM ClassReader for the class 506 * @return whether the class qualifies as a candidate component 507 */ 508 private boolean isConditionMatch(MetadataReader metadataReader) { 509 if (this.conditionEvaluator == null) { 510 this.conditionEvaluator = 511 new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver); 512 } 513 return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata()); 514 } 515 516 /** 517 * Determine whether the given bean definition qualifies as candidate. 518 * <p>The default implementation checks whether the class is not an interface 519 * and not dependent on an enclosing class. 520 * <p>Can be overridden in subclasses. 521 * @param beanDefinition the bean definition to check 522 * @return whether the bean definition qualifies as a candidate component 523 */ 524 protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { 525 AnnotationMetadata metadata = beanDefinition.getMetadata(); 526 return (metadata.isIndependent() && (metadata.isConcrete() || 527 (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())))); 528 } 529 530 531 /** 532 * Clear the local metadata cache, if any, removing all cached class metadata. 533 */ 534 public void clearCache() { 535 if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { 536 // Clear cache in externally provided MetadataReaderFactory; this is a no-op 537 // for a shared cache since it'll be cleared by the ApplicationContext. 538 ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); 539 } 540 } 541 542}