SoFunction
Updated on 2025-03-08

Analysis of the scanning process of Spring ComponentScan

Scanning process in XML

<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="/schema/beans"
	   xmlns:xsi="http:///2001/XMLSchema-instance"
	   xmlns:context="/schema/context"
	   xsi:schemaLocation="/schema/beans
	/schema/beans/ /schema/context /schema/context/"
	   default-lazy-init="false">
	<context:component-scan base-package=""/>
</beans>

Implemented with custom tag context in xml, and finally the() method will be called for parsing.

()

// #parse
public BeanDefinition parse(Element element, ParserContext parserContext) {
	// Get the package name specified by base-package	String basePackage = (BASE_PACKAGE_ATTRIBUTE);
	basePackage = ().getEnvironment().resolvePlaceholders(basePackage);
	// There may be multiple in base-package, separated by commas, converted to array	String[] basePackages = (basePackage,
			ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

	// Actually scan for bean definitions and register them.
	// Create a scanner	ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
	// Start scanning	Set&lt;BeanDefinitionHolder&gt; beanDefinitions = (basePackages);

	// Some components have been registered	// ConfigurationClassPostProcessor
	// AutowiredAnnotationBeanPostProcessor
	// CommonAnnotationBeanPostProcessor
	// EventListenerMethodProcessor
	// DefaultEventListenerFactory
	registerComponents((), beanDefinitions, element);

	return null;
}

ComponentScanBeanDefinitionParser#configureScanner

Create a scanner ClassPathBeanDefinitionScanner

protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
    boolean useDefaultFilters = true;
    if ((USE_DEFAULT_FILTERS_ATTRIBUTE)) {
        useDefaultFilters = ((USE_DEFAULT_FILTERS_ATTRIBUTE));
    }

    // Delegate bean definition registration to scanner class.
    // The default includeFilters is added to the ClassPathBeanDefinitionScanner constructor method as @Component    ClassPathBeanDefinitionScanner scanner = createScanner((), useDefaultFilters);
    (().getBeanDefinitionDefaults());
    (().getAutowireCandidatePatterns());

    if ((RESOURCE_PATTERN_ATTRIBUTE)) {
        ((RESOURCE_PATTERN_ATTRIBUTE));
    }

    try {
        parseBeanNameGenerator(element, scanner);
    }
    catch (Exception ex) {
        ().error((), (element), ());
    }

    try {
        parseScope(element, scanner);
    }
    catch (Exception ex) {
        ().error((), (element), ());
    }

    parseTypeFilters(element, scanner, parserContext);

    return scanner;
}

How do you know which annotations to scan?

The default Filter will be injected into the ClassPathBeanDefinitionScanner constructor.

protected void registerDefaultFilters() {
    // Only scan the classes annotated by @Component, and the annotations such as @Sevice, @Configuration, @Controller are all modified by @Component    (new AnnotationTypeFilter());
    ClassLoader cl = ();
    try {
        (new AnnotationTypeFilter(
                ((Class&lt;? extends Annotation&gt;) ("", cl)), false));
        ("JSR-250 '' 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 {
        (new AnnotationTypeFilter(
                ((Class&lt;? extends Annotation&gt;) ("", cl)), false));
        ("JSR-330 '' annotation found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
        // JSR-330 API not available - simply skip.
    }
}

In the source code, only the @Component annotation will be scanned, while the @Sevice, @Configuration, @Controller and other annotations are modified by @Component and will eventually be scanned.

ClassPathBeanDefinitionScanner#doScan

Start scanning:

// #doScan
protected Set&lt;BeanDefinitionHolder&gt; doScan(String... basePackages) {
	(basePackages, "At least one base package must be specified");
	Set&lt;BeanDefinitionHolder&gt; beanDefinitions = new LinkedHashSet&lt;&gt;();
	for (String basePackage : basePackages) {
		// Find all classes under basePackage that are modified by @Component annotation		Set&lt;BeanDefinition&gt; candidates = findCandidateComponents(basePackage);
		for (BeanDefinition candidate : candidates) {
			// The above findCandidateComponents only sets several properties for BD. Other properties of BD are not initialized, so you need to traverse the initialization properties and register them in the registry			ScopeMetadata scopeMetadata = (candidate);
			(());
			String beanName = (candidate, );
			if (candidate instanceof AbstractBeanDefinition) {
				postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
			}
			if (candidate instanceof AnnotatedBeanDefinition) {
				((AnnotatedBeanDefinition) candidate);
			}
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
				definitionHolder =
						(scopeMetadata, definitionHolder, );
				(definitionHolder);
				// Register to registry				registerBeanDefinition(definitionHolder, );
			}
		}
	}
	return beanDefinitions;
}

ClassPathScanningCandidateComponentProvider#scanCandidateComponents

Find all classes under basePackage that are modified by @Component annotation:

// #scanCandidateComponents
private Set&lt;BeanDefinition&gt; scanCandidateComponents(String basePackage) {
	Set&lt;BeanDefinition&gt; candidates = new LinkedHashSet&lt;&gt;();
	try {
		// basePackage=..service
		// packageSearchPath=classpath*:com/morris/spring/service/**/*.class
		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
			resolveBasePackage(basePackage) + '/' + ;
		// Get all class files under basePackage		Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
		boolean traceEnabled = ();
		boolean debugEnabled = ();
		for (Resource resource : resources) {
			if (traceEnabled) {
				("Scanning " + resource);
			}
			if (()) {
				try {
					// Use ASM to encapsulate the contents of the class file into a MetadataReader object					// Note that the reflection is not used here, the reflection will load the class and occupy heap space					MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
					// Determine whether the class contains @Component annotation					if (isCandidateComponent(metadataReader)) {
						// Package as BD						ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
						(resource);
						if (isCandidateComponent(sbd)) {
							if (debugEnabled) {
								("Identified candidate component class: " + resource);
							}
							(sbd);
						}
... ...
	return candidates;
}

registerAnnotationConfigProcessors

Registered with a variety of important components:

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
		BeanDefinitionRegistry registry, @Nullable Object source) {

	DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
	if (beanFactory != null) {
		if (!(() instanceof AnnotationAwareOrderComparator)) {
			();
		}
		if (!(() instanceof ContextAnnotationAutowireCandidateResolver)) {
			(new ContextAnnotationAutowireCandidateResolver());
		}
	}

	Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

	// BeanFactoryPostProcessor
	if (!(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition();
		(source);
		(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	// BeanPostProcessor
	if (!(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition();
		(source);
		(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
	if (jsr250Present && !(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition();
		(source);
		(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
	if (jpaPresent && !(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition();
		try {
			((PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
					()));
		}
		catch (ClassNotFoundException ex) {
			throw new IllegalStateException(
					"Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
		}
		(source);
		(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	// BeanFactoryPostProcessor
	if (!(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition();
		(source);
		(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
	}

	if (!(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition();
		(source);
		(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
	}

	return beanDefs;
}

The final result of the scan is to build a class with @Component annotation on the class into a BeanDefinition, and there are two collections in the Spring container to store these BeanDefinitions:

  • beanDefinitionNames: List<String>, store all the corresponding names of BeanDefinitions
  • beanDefinitionMap: Map<String, BeanDefinition>, store all BeanDefinitions

Annotation scanning process

Use of annotation scans:

package ;

import ;
import ;
import ;

@ComponentScan("")
public class ComponentScanDemo {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
		CityService cityService = ();
		();
	}
}

Use @ComponentScan annotation to specify the scan of the package, and the scanning process will be done by ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry.

ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	int registryId = (registry);
	if ((registryId)) {
		throw new IllegalStateException(
				"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
	}
	if ((registryId)) {
		throw new IllegalStateException(
				"postProcessBeanFactory already called on this post-processor against " + registry);
	}
	(registryId);
	// Focus	processConfigBeanDefinitions(registry);
}

ConfigurationClassPostProcessor#processConfigBeanDefinitions

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	List&lt;BeanDefinitionHolder&gt; configCandidates = new ArrayList&lt;&gt;();
	String[] candidateNames = ();
	// First collect the BDs with @Configuration, @Component, @ComponentScan, @Import, @ImportResource, @Bean	for (String beanName : candidateNames) {
		BeanDefinition beanDef = (beanName);
		if ((ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
			if (()) {
				("Bean definition has already been processed as a configuration class: " + beanDef);
			}
		}
		else if ((beanDef, )) {
			// You need @Component, @ComponentScan, @Import, @ImportResource, @Bean			(new BeanDefinitionHolder(beanDef, beanName));
		}
	}
    ... ...
	// Parse each @Configuration class
	ConfigurationClassParser parser = new ConfigurationClassParser(
			, , ,
			, , registry);
	Set&lt;BeanDefinitionHolder&gt; candidates = new LinkedHashSet&lt;&gt;(configCandidates);
	Set&lt;ConfigurationClass&gt; alreadyParsed = new HashSet&lt;&gt;(());
	do {
		// Start parsing		(candidates);
		();

ConfigurationClassParser#doProcessConfigurationClass

protected final SourceClass doProcessConfigurationClass(
		ConfigurationClass configClass, SourceClass sourceClass, Predicate&lt;String&gt; filter)
		throws IOException {
	... ...
	// Process any @ComponentScan annotations
	// Process @ComponentScan annotation, and the annotation with @Component under the scan package is consistent with the scanning process of the custom label context:component-scan in xml	Set&lt;AnnotationAttributes&gt; componentScans = (
			(), , );
	if (!() &amp;&amp;
			!((), ConfigurationPhase.REGISTER_BEAN)) {
		for (AnnotationAttributes componentScan : componentScans) {
			// The config class is annotated with @ComponentScan -&gt; perform the scan immediately
			Set&lt;BeanDefinitionHolder&gt; scannedBeanDefinitions =
					(componentScan, ().getClassName());
			// Check the set of scanned definitions for any further config classes and parse recursively if needed
			for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
				BeanDefinition bdCand = ().getOriginatingBeanDefinition();
				if (bdCand == null) {
					bdCand = ();
				}
				// CheckConfigurationClassCandidate will be handled specifically in @Configutation as full				if ((bdCand, )) {
					parse((), ());
				}
			}
		}
	}
	... ...
	return null;
}

ComponentScanAnnotationParser#parse

public Set&lt;BeanDefinitionHolder&gt; parse(AnnotationAttributes componentScan, final String declaringClass) {
	// parse @ComponentScan annotation and build ClassPathBeanDefinitionScanner	// The default includeFilters will be added to the build method as @Component	ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(,
			("useDefaultFilters"), , );
	Class&lt;? extends BeanNameGenerator&gt; generatorClass = ("nameGenerator");
	boolean useInheritedGenerator = ( == generatorClass);
	(useInheritedGenerator ?  :
			(generatorClass));
	ScopedProxyMode scopedProxyMode = ("scopedProxy");
	if (scopedProxyMode != ) {
		(scopedProxyMode);
	}
	else {
		Class&lt;? extends ScopeMetadataResolver&gt; resolverClass = ("scopeResolver");
		((resolverClass));
	}
	(("resourcePattern"));
	for (AnnotationAttributes filter : ("includeFilters")) {
		for (TypeFilter typeFilter : typeFiltersFor(filter)) {
			(typeFilter);
		}
	}
	for (AnnotationAttributes filter : ("excludeFilters")) {
		for (TypeFilter typeFilter : typeFiltersFor(filter)) {
			(typeFilter);
		}
	}
	boolean lazyInit = ("lazyInit");
	if (lazyInit) {
		().setLazyInit(true);
	}
	Set&lt;String&gt; basePackages = new LinkedHashSet&lt;&gt;();
	String[] basePackagesArray = ("basePackages");
	for (String pkg : basePackagesArray) {
		String[] tokenized = ((pkg),
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
		(basePackages, tokenized);
	}
	for (Class&lt;?&gt; clazz : ("basePackageClasses")) {
		((clazz));
	}
	if (()) {
		((declaringClass));
	}
	(new AbstractTypeHierarchyTraversingFilter(false, false) {
		@Override
		protected boolean matchClassName(String className) {
			return (className);
		}
	});
	// Start scanning	return ((basePackages));
}

It can be found that the annotated scan will call ClassPathBeanDefinitionScanner#doScan() at the end, which is the same method as the scan in XML.

Summarize

  • The XML scanning process occurs when obtainFreshBeanFactory(), that is, when creating a BeanFactory, and the annotated scanning process occurs when invokeBeanFactoryPostProcessors().
  • XML scans will inject ConfigurationClassPostProcessor when obtainFreshBeanFactory(), and the annotated scan is injected into ConfigurationClassPostProcessor when creating an AnnotationConfigApplicationContext instance. If the class scanned by the xml has the @ComponentScan annotation, it will continue to scan in the invokeBeanFactoryPostProcessors() stage.

This is the end of this article about the scanning process of spring ComponentScan. For more related spring ComponentScan scanning content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!