Spring IOC – ConfigurationClassPostProcessor source code analysis

As mentioned above, Spring will manually register 5 Processor classes into the beanDefinitionMap during the Bean scanning process. ConfigurationClassPostProcessor is what this article will explain. This class will be called in the refresh() method by calling invokeBeanFactoryPosstProcessors(beanFactory).

The list of 5 Processor classes is as follows:

Class name

Whether BeanDefinitionRegistryPostProcessor

Whether BeanFactoryPostProcessor

Whether BeanPostProcessor

ConfigurationClassPostProcessor

Yes

Yes

Yes

AutowiredAnnotationBeanPostProcessor

no

no

yes

CommonAnnotationBeanPostProcessor

no

no

yes

EventListenerMethodProcessor

no

yes

no

DefaultEventListenerFactory

no

no

no

Two core methods in ConfigurationClassPostProcessor will be called, and their main functions are as follows:

method name

effect

postProcessBeanDefinitionRegistry()

(1) @Conditional annotation conditional analysis

(2) Classes and internal classes marked by the following annotations

@Component, @PropertySources, @PropertySource, @ComponentScans, @ComponentScan, @Import, @ImportResource, @Bean, @Bean in the interface

postProcessBeanFactory()

CGLib enhancement to the full mode configuration class BeanDefinition: set the subtype generated by CGLib into the beanDefinition

The source code and comments of postProcessBeanDefinitionRegistry() are as follows:


@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
//Perform parsing and scanning
processConfigBeanDefinitions(registry);
}

Enter the processConfigBeanDefinitions(registry) method, and its main logic flow chart is as follows:

The steps marked in red are two key steps:

1. Determine whether it is a configuration class through beanDefinition

Spring specifically provides a tool class: ConfigurationClassUtils to check whether it is a configuration class and mark the full and lite modes. The logic is as follows:

2. Parse configuration class

doProcessConfigurationClass is the real parsing method for annotations. Its logic flow chart is as follows:

The specific source code and detailed comments are as follows:

1. Main process processConfigBeanDefinitions method:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// List of candidate configuration class definitions
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
// Get the names of all bean definitions in the container
String[] candidateNames = registry.getBeanDefinitionNames();

for (String beanName : candidateNames) {
// Get bean definition
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
// org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass
// Whether the Bean Definition has been marked as a configuration class (full, lite mode)
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
// Check whether the bean is ConfigurationClass
//Tagged by @Configuration, @Component, @ComponentScan, @Import, @ImportResource, @Bean
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
// If it is a configuration class, add it to the candidate list
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}

// Return immediately if no @Configuration classes were found
// If it is empty, that is, the class marked by @Configuration, @Component, @ComponentScan, @Import, @ImportResource, and @Bean cannot be found, it will return immediately
if (configCandidates.isEmpty()) {
return;
}

// Sort by previously determined @Order value, if applicable
// Sort the BeanDefinitions that need to be processed. The smaller the value, the higher it is.
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});

// Detect any custom bean name generation strategy supplied through the enclosing application context
// Check if there is a custom beanName generator
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
//Get the custom beanName generator
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
// If spring has a custom beanName generator, reassign it
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}

//If the environment object is empty, create a new environment object
if (this.environment == null) {
this.environment = new StandardEnvironment();
}

//Construct a configuration class parser to extract important information defined by the bean and convert it into ConfigurationClass
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);

// List of candidate configuration class definitions (duplication check)
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
// Store the parsed configuration class list
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// Parse configuration class
parser.parse(candidates);
// A configuration class cannot be declared final (due to CGLIB restrictions) unless it declares proxyBeanMethods=false
// In the @Configuration class, instances of @Bean methods must be overridable to accommodate CGLIB
parser.validate();

// Get the configuration class ConfigurationClass parsed in the parser: parser.getConfigurationClasses()
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
// Filter out parsed configuration classes
configClasses.removeAll(alreadyParsed);

// Construct a bean definition reader
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
//Read ConfigurationClass to obtain the derived bean definition and register it in the container
// Core method, convert the fully populated ConfigurationClass instance into a BeanDefinition and register it into the IOC container
this.reader.loadBeanDefinitions(configClasses);
//Add parsed configuration class
alreadyParsed.addAll(configClasses);
// Clear the list of candidate configuration class definitions
candidates.clear();
// If there are new bean definitions in the container
if (registry.getBeanDefinitionCount() > candidateNames.length) {
//Find the newly added configuration class bean definition start
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) & amp; & amp;
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());

// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
// If you want to use the ImportAware interface in the @Configuration class, you need to register ImportRegistry as a bean. This way, the @Configuration class can
//Use ImportRegistry to obtain imported class information
if (sbr != null & amp; & amp; !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}

if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
// If the MetadataReaderFactory is provided externally, its cache needs to be clear. However, if the MetadataReaderFactory is shared by the ApplicationContext, no clearing is required,
// Because ApplicationContext will automatically clear the shared cache
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}

2. Check whether it is a configuration class method ConfigurationClassUtils.checkConfigurationClassCandidate

//Check whether the given BeanDefinition is a candidate for a configuration class (or a nested component class declared in the configuration/component class)
//And mark it accordingly
//Tagged by @Configuration, @Component, @ComponentScan, @Import, @ImportResource, @Bean
public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {

//Get the class name in the bean definition information
String className = beanDef.getBeanClassName();
//If className is empty, or factoryMethod in the bean definition information is not equal to empty, then return directly
if (className == null || beanDef.getFactoryMethodName() != null) {
return false;
}

AnnotationMetadata metadata;
//The BeanDefinitions injected through annotations are all AnnotatedGenericBeanDefinition, which implements AnnotatedBeanDefinition.
//The BeanDefinitions inside Spring are all RootBeanDefiniton, which implements AbstractBeanDefinition.
//This is mainly used to determine whether it belongs to AnnotatedBeanDefinition.
if (beanDef instanceof AnnotatedBeanDefinition & amp; & amp;
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
// Can reuse the pre-parsed metadata from the given BeanDefinition...
// Get elements from the definition information of the current bean
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
//Determine whether it is the default BeanDefinition in Spring
else if (beanDef instanceof AbstractBeanDefinition & amp; & amp; ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
// Check already loaded Class if present...
// since we possibly can't even load the class file for this Class.
//Get the Class object of the current bean object
Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
//If the class instance is any of the following four categories or interface subclasses, parent interfaces, etc., return directly
if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
BeanPostProcessor.class.isAssignableFrom(beanClass) ||
AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
EventListenerFactory.class.isAssignableFrom(beanClass)) {
return false;
}
//Create a new AnnotationMetadata for the given class
metadata = AnnotationMetadata.introspect(beanClass);
}
//If the above two conditions are not met
else {
try {
//Get the MetadataReader instance of className
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
//Read the complete annotation metadata of the underlying class, including metadata of annotated methods
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: " +
className, ex);
}
return false;
}
}
// Get the attribute dictionary value of the Bean Definition's metadata annotated with @Configuration annotation
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
// If the bean is annotated with the @Configuration annotation and the value of the proxyBeanMethods attribute is true, then the current configuration class is marked as full, which requires proxy enhancement and is a proxy class.
if (config != null & amp; & amp; !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
// If it exists, or isConfigurationCandidate returns true, mark the current configuration class as lite, that is, it does not require agent enhancement and is a normal class.
// If the Configuration annotation is marked and the value of the proxyBeanMethods attribute is false, it is lite mode.
// If the Configuration annotation is not marked, but the Component, ComponentScan, Import, ImportResource, and @Bean annotations are marked, it is lite mode.
else if (config != null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
// The component class does not have the @Configuration annotation and returns false directly.
else {
return false;
}

// Get the sort order of the configuration class and set it to the component definition attribute
// It's a full or lite configuration candidate... Let's determine the order value, if any.
//Bean Definition is a candidate marked as full/lite. If there is an order attribute, set the order attribute.
Integer order = getOrder(metadata);
if (order != null) {
//Set the order value of the bean
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
}

return true;
}

3. The real work method doProessConfigurationClass

//Build a complete ConfigurationClass by reading the annotations, members and methods of the source class
// 1. Process @Component
// 2. Process each @PropertySource
// 3. Process each @ComponentScan
// 4. Process each Impoit
// 5. Process each @ImportResource
// 6. Find the @Bean annotated method in the configuration class (find the @Bean annotated method in Class and add it to the beanMethods attribute of ConfigurationClass)
// 7. Find the @Bean annotated method in the interface
// 8. If there is a parent class, recursive processing
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {

if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
// Recursively process nested member classes
processMemberClasses(configClass, sourceClass, filter);
}

// Process any @PropertySource annotations
// Handle @PropertySources and @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}

// Process any @ComponentScan annotations
// Handle @ComponentScans and @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() & amp; & amp;
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
// The configuration class uses the annotation @ComponentScan -> perform scanning immediately.
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.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());
}
}
}
}

// Process any @Import annotations
// Handle @Import annotation
// getImports: Recursively obtain the classes imported by @Import
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

// Process any @ImportResource annotations
// Process the @ImportResource annotation
// Read locations and reader attributes, encapsulate and store them in the importResources map collection
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}

// Process individual @Bean methods
// Handle @Bean method
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

// Process default methods on interfaces
// Handle the default method implementation of the interface. Starting from jdk8, the methods in the interface can have their own default implementation, so if the method of this interface is annotated with @Bean, it also needs to be parsed.
processInterfaces(configClass, sourceClass);

// Process superclass, if any
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null & amp; & amp; !superclass.startsWith("java") & amp; & amp;
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}

// No superclass -> processing is complete
return null;
}