SoFunction
Updated on 2025-03-04

Spring's detailed process of parsing configuration classes and scanning package paths

Target

This is the core code for us to start the spring container using annotation

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
User user = (User) ("user");
();

The code for configuring the class MyConfig is

@ComponentScan(value = "")
public class MyConfig {
}

Now our goal is to figure out how spring parses this configuration class and scans the beans under the package path of the configuration class?

Important components

  • AnnotatedBeanDefinitionReader: This reader will be created when the spring container is started, mainly to save the class to the bean factory in the form of BeanDefinition (DefaultListableBeanFactory)
    When creating this reader, spring will add a defaultConfigurationClassPostProcessorBeanDefinition, which is the main object when parsing the configuration class, is implemented in registerAnnotationConfigProcessors of the AnnotationConfigUtils class
if (!(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    RootBeanDefinition def = new RootBeanDefinition();
    (source);
    (registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
  • ClassPathBeanDefinitionScanner: Path scanner, created when spring starts. Its main function is to scan the classpath, which contains some scanning rules. For example, a filter with Component annotation is built in when creating.
protected void registerDefaultFilters() {
    (new AnnotationTypeFilter());
    ...
}

Loading configuration classes

Our configuration class is converted into BeanDefinitionReader class doRegisterBean method, converted into BeanDefinition and stored in beanDefinitionMap of bean factory, and obtained a class information based on ASM and converted into BeanDefinition.
The core code converted to

AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);

After obtaining the AnnotatedGenericBeanDefinition of the configuration class object, although the class has not been loaded, the class annotation information has been obtained.
Although they all have BeanDefinitions, the BeanDefinition saved to the bean factory is different from this. This AnnotatedGenericBeanDefinition mainly contains some annotation information and does not have properties similar to BeanDefinition, such as whether it is lazy to load, scope, dependency, etc.

The main code for parsing AnnotatedGenericBeanDefinition annotation information is to read Lazy, Primary, DependsOn, Description set to attribute values

AnnotationAttributes lazy = attributesFor(metadata, );
if (lazy != null) {
	(("value"));
}
else if (() != metadata) {
	lazy = attributesFor((), );
	if (lazy != null) {
		(("value"));
	}
}

if ((())) {
	(true);
}
AnnotationAttributes dependsOn = attributesFor(metadata, );
if (dependsOn != null) {
	(("value"));
}

AnnotationAttributes role = attributesFor(metadata, );
if (role != null) {
	(("value").intValue());
}
AnnotationAttributes description = attributesFor(metadata, );
if (description != null) {
	(("value"));
}

The BeanDefinition we want to save to the bean factory is the BeanDefinition we want to save to the bean factory.

BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);

If the configuration class is not a proxy mode, save the BeanDefinition directly into the bean factory.
If it is proxy mode, create a new RootBeanDefinition and save it to the bean factory. The main code implemented is in the ScopedProxyUtils class createScopedProxy method

Start parsing components

When spring starts a configuration class scanning task, it calls the scanning class to execute by starting a BeanDefinitionRegistryPostProcessor, which belongs to a component-based method of starting a task class.

for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
	...
	(registry);
	...
}

The implementation class of this component is ConfigurationClassPostProcessor, so all scanning codes are under the postProcessBeanDefinitionRegistry method of this class

Positioning configuration class

In the beanDefinitionMap of the bean factory, traverse each element to locate the bd that matches the configuration class. The rule verification is in the ConfigurationClassUtils class checkConfigurationClassCandidate method:

  1. It is mainly to determine that the bd is of AnnotatedBeanDefinition type.
  2. If the beanDef is not an instance of AnnotatedBeanDefinition, further check whether it is an instance of AbstractBeanDefinition and already has the corresponding Class object. If so, then check whether this Class implements certain interfaces (such as BeanFactoryPostProcessor, BeanPostProcessor, AopInfrastructureBean, or EventListenerFactory). If one or more of these interfaces are indeed implemented, the function returns false, indicating that there is no need to continue parsing. Otherwise, it will obtain the annotation metadata of the class through the (beanClass) method.
  3. If neither of the above situations is satisfied, the code will try to read the metadata of the specified class name (className) from the classpath through the MetadataReader. This usually involves loading the class file and extracting information from it. If an IO exception occurs during this process (for example, the class file cannot be found), the error message is logged and false is returned.

Resolve configuration class

The parsing operation is done by ConfigurationClassParser. All the parsing related logic is in the processConfigurationClass method of this class, which is mainly responsible for parsing and registering various annotations in the configuration class:
Process @PropertySource @ComponentScan @Import @ImportResour @Bean annotation. Here the value analysis @ComponentScan annotation. Because the meta information of the class has been obtained, you can obtain the path configured by @ComponentScan, and then perform path scanning. The scan is executed by the ComponentScanAnnotationParser component, and is initiated by the ComponentScanAnnotationParser component and finally implemented in the ClassPathBeanDefinitionScanner type doScan

Scanning process

Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

By calling the findCandidateComponents method, all classes under the package and its subpackage that meet the component scanning conditions are found according to the provided basePackage, and return them as candidate components. Each candidate component is a BeanDefinition object representing a potential Spring bean:

  • Build a search path:
    Build a resource pattern path that indicates where ResourcePatternResolver is looking for resources. This path includes classpath prefix, base package name, and resource schema (for example /**/*.class) to facilitate matching of all class files.
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + '/' + ;
  • Get resources
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);

Get the resource parser instance through getResourcePatternResolver() and call its getResources method to get all resources that match the given pattern. The resources here refer to class files that conform to the path pattern.

  • Preliminary screening
    Iterate through each resource, and use the MetadataReaderFactory to create a MetadataReader instance for each resource. It can read the metadata of the class without loading the class into the JVM.
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
(resource);

First, use isCandidateComponent (metadataReader) method to initially determine whether the resource may be a candidate component:

AnnotationMetadata metadata = ();
return (() && (() ||
				(() && (()))));
  1. Classes must be independent (non-inner classes).
  2. At the same time, the class must be concrete (non-interface or non-abstract class), or if it is an abstract class, it must contain at least one method marked with @Lookup annotation.
  • Determine whether to create as a BeanDefinition
ScopeMetadata scopeMetadata = (candidate);
(());
String beanName = (candidate, );

For each candidate BeanDefinition, use scopeMetadataResolver to parse its scope (scope) information, and generate or obtain a unique beanName for the bean.

if (candidate instanceof AbstractBeanDefinition) {
    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}

If the candidate Bean is an instance of the AbstractBeanDefinition type, call the postProcessBeanDefinition method for additional post-processing, such as applying default values ​​and automatic assembly rules.

if (candidate instanceof AnnotatedBeanDefinition) {
	((AnnotatedBeanDefinition) candidate);
}

If the candidate bean is of type AnnotatedBeanDefinition, common annotations such as @Lazy, @Primary, @DependsOn, @Role, and @Description will be processed

if (checkCandidate(beanName, candidate)) {
	BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
	definitionHolder =
			(scopeMetadata, definitionHolder, );
	(definitionHolder);
	registerBeanDefinition(definitionHolder, );
}

Check whether the current candidate bean can be registered in the container. If so, continue to perform the following operations:
Create a BeanDefinitionHolder object that holds the Bean definition, Bean name, and other metadata.
If you need to use applyScopedProxyMode to create scoped proxy based on scoped proxy mode,
Add the processed BeanDefinitionHolder to the beanDefinitions list and register it in the registry.

There is another method in checkCandidate

protected boolean isCompatible(BeanDefinition newDef, BeanDefinition existingDef) {
	return (!(existingDef instanceof ScannedGenericBeanDefinition) ||  // explicitly registered overriding bean
			(() != null && ().equals(())) ||  // scanned same file twice
			(existingDef));  // scanned equivalent class twice
}

Check whether the new bean definition is compatible with the existing bean definition to avoid conflicts caused by repeated scanning of the same file or class.

Summarize

  1. Configuration class loading: Use AnnotatedBeanDefinitionReader to convert the configuration class to BeanDefinition and obtain class information through the ASM library.
  2. Start parsing component: Start parsing tasks of configuration classes by implementing the ConfigurationClassPostProcessor component of the BeanDefinitionRegistryPostProcessor interface.
  3. Positioning and parsing configuration classes: Iterate through all BeanDefinitions in the bean factory to locate the configuration class and use ConfigurationClassParser to handle various annotations on the configuration class, such as @ComponentScan.
  4. Component scanning: ClassPathBeanDefinitionScanner finds classes that meet component scanning conditions based on the specified basic package name, performs preliminary filtering and creates a BeanDefinition object, and finally registers it in the Spring container.

The above is the detailed content of the detailed process of Spring parsing configuration classes and scanning package paths. For more information about Spring parsing configuration classes and scanning package paths, please pay attention to my other related articles!