ComponentScan source code parsing

Blog index

The following source analysis version is Spring 5.2.5 Release

Understand @ Component

@Component is a generic Component hosted by the Spring container. Any Component annotated by @Component is an object scanned by the Component. Components like @repository, @Service, or use the @Component annotation as a custom annotation.

//@see org.springframework.context.annotation.ClassPathBeanDefinitionScanner
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	String value() default "";

}
Copy the code

1. The use of@RepositoryDefine a custom annotation

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//@Component
@Repository
public @interface StringRepository {

    String value() default "";
}
Copy the code

Write a test classNameRepository, add the note

@StringRepository("chineseNameRepository") public class NameRepository { public List<String> findAll() { return AsList (" Zhang SAN ", "Li Si "," Wang Ermazi "); }}Copy the code

3. The testNameRepositoryWhether the class can be loaded by the Spring container

Create a test class ComponentBootStrap with the following code

@Configuration @ComponentScan public class ComponentBootStrap { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ComponentBootStrap.class); // context.register(ComponentBootStrap.class); // context.refresh(); NameRepository nameRepository = context.getBean(NameRepository.class); System.out.println("nameRepository.findAll() = " + nameRepository.findAll()); }}Copy the code

Tip: The NameRepository class and the bootstrap class ComponentBootStrap need to be placed in the same package to be scanned and loaded by @ComponentScan. The output is: namerepository.findall () = [Threethreoritory.findall () = [Threethreoritory.findAll () = [Threethreoritory.findAll () = [Threethreoritory.findAll () = [Threethreoritory.findAll () = [Threethreoritory.findAll () =]. Conclusion: As long as your annotation contains @Component, it can be hosted by the Spring container. Because @Component is a meta-annotation, you can combine meta-annotations to create composite annotations, such as @RestController, which is composed of @Controller and @responseBody. Website beans – meta – annotations

The above example comes from Markey’s Spring Boot Ideas. The NameRepository annotation does not inherit from @Component, but its effect does. Markey defines this schema annotation as the @Component lineage. Let’s explore how @Component generativity works.

To explore the source code

Any annotation that contains @Component will be scanned by Spring and registered. Why is that? Does it have something to do with scanning? Spring scans beans in two ways, />
The base-package value is the package name of NameRepository, and the @ComponentScan annotation, If no value is entered, all classes in the annotation class package are scanned by default.

The first way to customize tags is not discussed here for the time being. I will write a special article to analyze it later. We’ll focus on the annotation @ComponentScan method here.

1. The first look@ComponentScanThe source code

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Repeatable(ComponentScans.class) public @interface ComponentScan { @AliasFor("basePackages") String[] value() default {}; @AliasFor("value") String[] basePackages() default {}; Class<? >[] basePackageClasses() default {}; Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class; ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN; Boolean useDefaultFilters() default true; Boolean useDefaultFilters() default true; Filter[] includeFilters() default {}; Filter[] excludeFilters() default {}; boolean lazyInit() default false; @Retention(RetentionPolicy.RUNTIME) @Target({}) @interface Filter { FilterType type() default FilterType.ANNOTATION; @AliasFor("classes") Class<? >[] value() default {}; @AliasFor("value") Class<? >[] classes() default {}; String[] pattern() default {}; }}Copy the code

There are many attributes in this annotation, which I will not introduce here, if you are interested in their own information. There’s a note @aliasfor (“value”) that comes up a lot, and I’ll write a separate post about it next time if you’re interested. Off topic, let’s explore, where does Spring scan? This will confuse many of you, but my method is to start with idea’s Find Lead shortcut, CTRL+G, to search where this annotation is invoked, as follows

Then go to the usage in. Class and look for the relationshiporg.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClassParsing is found in this method@ComponentScanPlace. But take a quick look at this class and, boy, it doesn’t just parse what we’re talking about@ComponentScanAnd parsing@Import.@Bean.@PropertySources.@ComponentScans.@ImportResourceAnd so on. Next time we’ll talk about this class once and for all.

As the key to see the core code ConfigurationClassParser# doProcessConfigurationClass:

protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter){··· · // Process any @ComponentScan Annotations // Obtain ComponentScans, ComponentScan annotation inside all of the attributes Set < AnnotationAttributes > componentScans = AnnotationConfigUtils. AttributesForRepeatable ( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (! componentScans.isEmpty() && ! this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : ComponentScans) {// The config class is annotated with @componentScan -> Perform The scan immediately The following key analytical Set < BeanDefinitionHolder > scannedBeanDefinitions = this.com ponentScanParser. Parse (componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); }}}} ··Copy the code

ComponentScanAnnotationParser#parse(AnnotationAttributes,String)

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, Final String declaringClass) {// I new a scanner object, because useDefaultFilters in @ComponentScan default to true, So the value passed in here is true, which means the default filter is used in the constructor, The following will introduce ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner (this registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator"); boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass); scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass)); ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy"); if (scopedProxyMode ! = ScopedProxyMode.DEFAULT) { scanner.setScopedProxyMode(scopedProxyMode); } else { Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver"); scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass)); } scanner.setResourcePattern(componentScan.getString("resourcePattern")); for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addIncludeFilter(typeFilter); } } for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addExcludeFilter(typeFilter); } } boolean lazyInit = componentScan.getBoolean("lazyInit"); if (lazyInit) { scanner.getBeanDefinitionDefaults().setLazyInit(true); } Set<String> basePackages = new LinkedHashSet<>(); String[] basePackagesArray = componentScan.getStringArray("basePackages"); for (String pkg : basePackagesArray) { String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); Collections.addAll(basePackages, tokenized); } for (Class<? > clazz : componentScan.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } if (basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(declaringClass)); } scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { @Override protected boolean matchClassName(String className) { return declaringClass.equals(className); }}); // Assign the attributes in the annotation to scanner, Then parsing methods entrusted to ClassPathBeanDefinitionScanner# doScan return scanner. DoScan (StringUtils. ToStringArray (basePackages)); }Copy the code

org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan

protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); // basePackages = @scanComponent for (String basePackage: BasePackages) {// Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); // Register candidate components as BeanDefinition registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }Copy the code

Scan candidate component class path ClassPathScanningCandidateComponentProvider# findCandidateComponents (String)

public Set<BeanDefinition> findCandidateComponents(String basePackage) { if (this.componentsIndex ! = null && indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { return scanCandidateComponents(basePackage); } } private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { // String CLASSPATH_ALL_URL_PREFIX = "classpath*:"; / / if the basePackage here is com. Example. Learnspring, resolveBasePackage (basePackage) package name into com/example/learnspring / / ResolveBasePackage (basePackage) replaces the "." in the package name with a "/", String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + "**/*.class"; / / according to the incoming parsed into the path of the Resource the Resource [] resources = getResourcePatternResolver () getResources (packageSearchPath); If (resource.isreadable ()) {try {// generate a facade class that accesses metadata MetadataReader MetadataReader MetadataReader = getMetadataReaderFactory().getMetadataReader(resource); / / determine whether as candidates for the if (isCandidateComponent (metadataReader)) {ScannedGenericBeanDefinition SBD = new ScannedGenericBeanDefinition(metadataReader); sbd.setResource(resource); sbd.setSource(resource); if (isCandidateComponent(sbd)) { candidates.add(sbd); } else { if (debugEnabled) { logger.debug("Ignored because not a concrete top-level class: " + resource); }}}}} Catch (Throwable ex) {···} return candidates; }Copy the code

Let’s start with MetadataReader, which is a facade class that accesses class metadata through ASM. In this case, the implementation class is SimpleMetadataReader. Metadata is SimpleAnnotationMetadataReadingVisitor by ASM.

final class SimpleMetadataReader implements MetadataReader { private static final int PARSING_OPTIONS = ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES; private final Resource resource; // Data metadata, including className,superClassName, etc. AnnotationMetadata private Final AnnotationMetadata AnnotationMetadata; SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException { // Created by SimpleAnnotationMetadataReadingVisitor AnnotationMetadata, principle is the ASM, students interested in here can make a breakpoint here, To debug SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor (this); getClassReader(resource).accept(visitor, PARSING_OPTIONS); this.resource = resource; this.annotationMetadata = visitor.getMetadata(); }Copy the code

Then determine if it is a candidate

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
	for (TypeFilter tf : this.excludeFilters) {
		if (tf.match(metadataReader, getMetadataReaderFactory())) {
			return false;
		}
	}
	for (TypeFilter tf : this.includeFilters) {
		if (tf.match(metadataReader, getMetadataReaderFactory())) {
			return isConditionMatch(metadataReader);
		}
	}
	return false;
}
Copy the code

Our class StringRepository has been successfully loaded by Spring, so it must return true here, so make a breakpoint here to debug. excludeFilters: Exclude meta information with annotations includeFilters: IncludeFilters contain two AnnotationTypeFilters, one containing Component and the other containing ManagedBean. This is because the default filters are registered in the registerDefaultFilters method.

protected void registerDefaultFilters() { this.includeFilters.add(new AnnotationTypeFilter(Component.class)); ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); try { this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); } catch (ClassNotFoundException ex) {// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.} try {  this.includeFilters.add(new AnnotationTypeFilter( ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false)); logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); } catch (ClassNotFoundException ex) { // JSR-330 API not available - simply skip. } }Copy the code

Search for where to call registerDefaultFilters, found ClassPathBeanDefinitionScanner# ClassPathBeanDefinitionScanner ()

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, Environment environment, @Nullable ResourceLoader resourceLoader) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry; If (useDefaultFilters) {// Set the default filter registerDefaultFilters(); } setEnvironment(environment); setResourceLoader(resourceLoader); }Copy the code

Finally found in ClassPathBeanDefinitionScanner constructor sets the default filter, which is in ComponentScanAnnotationParser# parse method of the first row, Create ClassPathBeanDefinitionScanner object, and the above form echo.

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
				componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
Copy the code

This is getting a little confusing, but let’s clean it up a little bit. We just said that the check is a candidate, so the match() method below returns true. Guess from the method name that any class that contains the annotation @Component or @ManageBean will match. Let’s continue our in-depth source code analysis.

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { return false; } } for (TypeFilter tf : This.includefilters) {if (tf.match(metadataReader, GetMetadataReaderFactory ()) {return isConditionMatch(metadataReader); } } return false; }Copy the code

AbstractTypeHierarchyTraversingFilter#match(MetadataReader,MetadataReaderFactory)

public boolean match(MetadataReader metadataReader, Throws IOException {MetadataReaderFactory MetadataReaderFactory throws IOException { If (matchSelf(metadataReader)) {return true; }...... return false. } protected Boolean matchSelf(MetadataReader MetadataReader) { Here is the custom annotation @ AnnotationMetadata StringRepository information metadata. = metadataReader getAnnotationMetadata (); / / the following this. AnnotationType. GetName () is introduced to org.springframework.stereotype.Com ponent return metadata.hasAnnotation(this.annotationType.getName()) || (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName())); }Copy the code

AnnotationMetadata#hasMetaAnnotation

Default Boolean hasMetaAnnotation(String metaAnnotationName) {return getAnnotations().get(metaAnnotationName, MergedAnnotation::isMetaPresent).isPresent(); }Copy the code

Here, is already very clear, the only two methods, one is the metadata. HasAnnotation (this) annotationType) getName ()), which is to determine whether a class containing a given annotations, Another way is to the metadata. HasMetaAnnotation (this) annotationType) getName ()), it is whether the bottom class contains a given annotation, And @StringRepository does contain @Component underneath, so this method returns true.

Conditionmatch (MetadataReader) is a method to determine whether Conditional annotations are met in the metadata

If there is any doubt or write a bad place, you can comment or contact me via email [email protected]