ConfigurationClassPostProcessor source code parsing

Scan the qr code at the end of this article or search the wechat official account, you can follow the wechat official account, rookie Fei Yafei, read more Spring source analysis articles


0. Confused

Here are some questions to consider before reading this article.

  • (1) What is the use of the @Configuration annotation, and how does Spring parse classes with @Configuration annotations?
  • (2) When did Spring parse @ComponentScan and @ComponentScans?
  • (3) When and how did Spring parse the @import annotation?
  • (4) When did Spring parse the @bean annotation?

1. The role

In fact, the above four issues, can be explained, a class is the leading role of this article: ConfigurationClassPostProcessor. So how does this class solve these problems?

  • ConfigurationClassPostProcessor is a rear the BeanFactory processors, therefore its main function is to participate in the construction of the BeanFactory, in this class, parses @ the Configuration of the Configuration class, It also resolves @ComponentScan, @ComponentScans, and @import annotations.

  • ConfigurationClassPostProcessor BeanDefinitionRegistryPostProcessor interface is achieved, And BeanDefinitionRegistryPostProcessor interface inherits the spring BeanFactoryPostProcessor interface, So needs to be rewritten in the ConfigurationClassPostProcessor postProcessBeanDefinitionRegistry () method and postProcessBeanFactory () method. And ConfigurationClassPostProcessor class effect is by these two methods to achieve.

  • ConfigurationClassPostProcessor this class is a Spring inside the BeanFactory post processor, Is this () method will be added to the BeanDefinitionMap (you can refer to the author’s another article mp.weixin.qq.com/s/q6zs7xRjp…). . In the process of execution, will first perform postProcessBeanDefinitionRegistry (), and then execute postProcessBeanFactory ().

  • The article is a bit long, so let’s start with a diagram to briefly describe the implementation process.

2. postProcessBeanDefinitionRegistry()

  • PostProcessBeanDefinitionRegistry () method calls the processConfigBeanDefinitions (), so the core logic in processConfigBeanDefinition () method.
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry){
    processConfigBeanDefinitions(registry);
}
Copy the code

ProcessConfigBeanDefinitions () method code is as follows (omit part of the code is not important), the source code to add a lot of comments, explains some important way.

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
	String[] candidateNames = registry.getBeanDefinitionNames();

	for (String beanName : candidateNames) {
		BeanDefinition beanDef = registry.getBeanDefinition(beanName);
		if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
				ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
			/ / the log log
		}
        / / checkConfigurationClassCandidate () will determine whether a is a configuration class, and set properties for BeanDefinition as lite or full.
        // Here we set the lite and full values for BeanDefinition for later use
        // If @configuration is added, the BeanDefinition is full;
        // Lite with @bean,@Component,@ComponentScan, @import, @importResource annotations.
        Lite and full both indicate that the BeanDefinition class is a configuration class
		else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
			configCandidates.add(newBeanDefinitionHolder(beanDef, beanName)); }}/ /... Omit some code
	SingletonBeanRegistry sbr = null;
	if (registry instanceof SingletonBeanRegistry) {
		sbr = (SingletonBeanRegistry) registry;
		if (!this.localBeanNameGeneratorSet) {
			// beanName generator, because it will scan all the calss classes added to the Spring container, and then add those classes
			// Parse to BeanDefinition classes, then use BeanNameGenerator to generate beanName for these BeanDefinitions
			BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
			if(generator ! =null) {
				this.componentScanBeanNameGenerator = generator;
				this.importBeanNameGenerator = generator; }}}/ /... Omit some code

	// Parse all classes annotated with @Configuration
	ConfigurationClassParser parser = new ConfigurationClassParser(
			this.metadataReaderFactory, this.problemReporter, this.environment,
			this.resourceLoader, this.componentScanBeanNameGenerator, registry);

	Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
	Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
	do {
		// Parse configuration classes, where annotations on configuration classes are resolved (classes scanned by ComponentScan, classes registered by @import, and classes defined by @bean methods)
        // Note: This step adds BeanDefinitionMap only classes with @Configuration annotations and classes scanned by @ComponentScan annotations
        // With other annotations (e.g. @import, @bean), the Parse () method step does not parse BeanDefinitionMap to BeanDefinitionMap, but first to the ConfigurationClass class
        / / into the real map is below this. Reader. LoadBeanDefinitions () method
		parser.parse(candidates);
		parser.validate();

		Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
		configClasses.removeAll(alreadyParsed);

		// 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());
		}
		/ / will step on the parser parsed the ConfigurationClass BeanDefinition class loading
		// Actually after the previous step parse(), the parsed beans are already in the BeanDefinition, but since they may introduce new beans, Realized ImportBeanDefinitionRegistrar or ImportSelector interface beans, for example, or exist in the bean is @ bean annotation methods
		/ / so you need to perform a loadBeanDefinition (), that will be executed ImportBeanDefinitionRegistrar ImportSelector interface method or @ Bean annotation methods
		this.reader.loadBeanDefinitions(configClasses);
		alreadyParsed.addAll(configClasses);

		candidates.clear();
		/ / here judge registry. GetBeanDefinitionCount () > CandidateNames. The length of the purpose is to know the reader. LoadBeanDefinitions (configClasses) did this step to add a new BeanDefinition BeanDefinitionMap
		// Just look at the configuration class (e.g. the AppConfig class adds beans to BeanDefinitionMap)
		/ / if you have, registry. GetBeanDefinitionCount () will be greater than candidateNames. Length
		// It is necessary to iterate over the newly added BeanDefinition and determine if the beans have already been parsed. If not, it needs to be parsed again
		// The beans that the AppConfig class here adds to the container are actually all parsed in the parser.parse() step
		// So why do we still need to make this judgment?
		if (registry.getBeanDefinitionCount() > candidateNames.length) {
			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());
			}
			// If there are unparsed classes, add them to the candidates so that the candidates are not empty and enter the next while loop
			for (String candidateName : newCandidateNames) {
				if(! oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName);if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && ! alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(newBeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; }}while(! candidates.isEmpty());// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
	if(sbr ! =null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
		sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
	}

	if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
		((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); }}Copy the code
2.1 ConfigurationClassUtils. CheckConfigurationClassCandidate ()
  • This method is used to determine if a is a configuration class and isBeanDefinitionSet the property toliteorfull. If added@ConfigurationAnd then the correspondingBeanDefinitionforfullIf you add@Bean.@Component.@ComponentScan.@Import.@ImportResourceThese notes arelite.liteandfullAll represent thisBeanDefinitionThe corresponding class is a configuration class.
  • Part of the code is as follows:
public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
	/ /... Omit unimportant code
	if (isFullConfigurationCandidate(metadata)) {
		// With the @Configuration annotation, the corresponding BeanDefinition's configurationClass property is set to full
		beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
	}
	else if (isLiteConfigurationCandidate(metadata)) {
		// contains @bean,@Component,@ComponentScan, @import, @importResource annotations
		// Set the configurationClass property value to Lite
		beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
	}
	else {
		return false;
	}
	return true;
}
Copy the code
  • isFullConfigurationCandidate()The method is used to determine whether a class has been added@ Configuration annotations
// Contains the @configuration annotation
public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
	return metadata.isAnnotated(Configuration.class.getName());
}
Copy the code
  • isLiteConfigurationCandidate()The method is used to determine whether a class is added@Bean,@Component,@ComponentScan,@Import,@ImportResourceannotations
// Check if there are annotations from the collection candidateIndicators
public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
	// candidateIndicators is a static constant that, when initialized, contains four elements
	@component,@ComponentScan, @import, @importResource
	// If one of the four annotations is added to the class, the class is a configuration class.
	// The configurationClass attribute in BeanDefinition is Lite
	for (String indicator : candidateIndicators) {
		if (metadata.isAnnotated(indicator)) {
			return true; }}// Look for methods annotated with @bean
	try {
		return metadata.hasAnnotatedMethods(Bean.class.getName());
	}
	catch (Throwable ex) {
		return false; }}private static final Set<String> candidateIndicators = new HashSet<>(8);

When the class is loaded into the JVM, four elements are added to the collection
static {
	candidateIndicators.add(Component.class.getName());
	candidateIndicators.add(ComponentScan.class.getName());
	candidateIndicators.add(Import.class.getName());
	candidateIndicators.add(ImportResource.class.getName());
}
Copy the code
2.2 parser. Pase ()

This method is invoked ConfigurationClassParser. The parse (), ConfigurationClassParser class, according to the name of the class can guess, this class is used to resolve the configuration class.

  • The parse() method parses annotations on configuration classes (classes scanned by ComponentScan, classes registered with @Import, and classes defined by the @Bean method). Once it’s resolved (to the ConfigurationClass class), The parsed results are put into the configurationClasses attribute of the Parser (which is a Map). Parse interprets the @import annotation to be registered as BeanDefinitionMap, but does not put parsed BeanDefinitionMap in this line: this.reader.loadBeanDefinitions(configClasses)

  • Parse () requires a parameter. The parameter candidates are a set of arguments that are determined by this line:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
Copy the code
  • inAnnotationConfigApplicationContextThe candidates must be of size 1, and the candidates must be of size 1. AppConfig for the element inside the corresponding BeanDefinitionHolder (or BeanDefinition BeanDefinitionHolder just BeanDefinition encapsulation and can simply think the equivalent). AnnotationConfigApplicationContext construction method can be introduced into multiple classes, the size of the corresponding candidates equals the number of incoming class here (this statement is not too serious, becauseAnnotationConfigApplicationContext.register()Methods can also register configuration classes in containers.
  • Parse () specifies the code
public void parse(Set<BeanDefinitionHolder> configCandidates) {
	this.deferredImportSelectors = new LinkedList<>();
    Parse () calls different overloaded methods depending on the BeanDefinition type
    / / in fact are ultimately call processConfigurationClass () method
	for (BeanDefinitionHolder holder : configCandidates) {
		BeanDefinition bd = holder.getBeanDefinition();
		try {
			if (bd instanceof AnnotatedBeanDefinition) {
				parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
			}else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
				parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
			}else{ parse(bd.getBeanClassName(), holder.getBeanName()); }}}// Handle delayed importSelector
	processDeferredImportSelectors();
}
Copy the code
2.2.1 processConfigurationClass () the core code

The core method for doProcessConfigurationClass of the method

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
	// Process configuration classes. Since configuration classes may have a parent (except if the full class name begins with Java), you need to parse configClass as sourceClass and return the parent of sourceClass.
	// If the parent is empty, the while loop will not parse, if the parent is not empty, the loop will parse the parent
	// The significance of SourceClass: a simple wrapper around classes so that annotated classes can be handled in a uniform way, regardless of how they are loaded
	// If you can't understand it, you can use it as a black box, which will not affect the main flow of spring source code
	SourceClass sourceClass = asSourceClass(configClass);
	do {
    // Core processing logic
		sourceClass = doProcessConfigurationClass(configClass, sourceClass);
	}
	while(sourceClass ! =null);
    // Store the parsed configuration class so that the value can be retrieved when you return to the parse() method
	this.configurationClasses.put(configClass, configClass);
}
Copy the code
2.2.2 doProcessConfigurationClass () code

DoProcessConfigurationClass () method, execution process is as follows:

  • (1) Processing inner class, if the inner class is also a configuration class (to determine whether a class is a configuration class, throughConfigurationClassUtils.checkConfigurationClassCandidate()Can be judged).
  • (2) Processing property resource file, added@PropertySourceAnnotation.
  • (3) first parse the @ComponentScan and @ComponentScans annotation on the class, and then according to the configuration of the scan package path, using ASM technology (ASM technology is a kind of operation bytecode technology, interested friends can go online to understand) scan all the classes that need to be handed over to Spring management, Since the scanned classes may also be annotated with @ComponentScan and @ComponentScans, recursive resolution is required until all classes with these two annotations are resolved.
  • (4) Handle @import annotations. With the @import annotation, there are three ways to register a Bean into the Spring container.
  • (5) Handle @importResource annotations and parse configuration files.
  • (6) Handle @bean annotated methods.
  • (7)processInterfaces()The default methods for handling interfaces, starting with JDK8, can have their own default implementation, so if the methods in this interface are also annotated with @bean, they need to be resolved as well. (Rarely used)
  • (8) Parse the parent class. If the configuration class being parsed inherits a class, the parent class of the configuration class will also be parseddoProcessConfigurationClass()(The parent class is an exception to the BUILT-IN JDK class, that is, the full class name begins with Java.)

For step (7), let me give you an example. In the following code example, the AppConfig class, annotated with Configuration, is a Configuration class that implements the AppConfigInterface interface. This interface has a default implementation method (starting with JDK8, methods in the interface can have default implementations) with the @bean annotation on it. At this point, after the parsing in step (7), you add a bean of type InterfaceMethodBean to the Spring container.

@Configuration
public class AppConfig implements AppConfigInterface{}public interface AppConfigInterface {
	@Bean
	default InterfaceMethodBean interfaceMethodBean(a) {
		return newInterfaceMethodBean(); }}Copy the code

DoProcessConfigurationClass () of the source code is as follows, the source code to add Chinese annotation

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
		throws IOException {

	/ / 1, the first treatment inner classes, inner classes, eventually call doProcessConfigurationClass () method
	processMemberClasses(configClass, sourceClass);
	// add @propertysource annotation to PropertySource file
	for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
			sourceClass.getMetadata(), PropertySources.class,
			org.springframework.context.annotation.PropertySource.class)) {
		if (this.environment instanceofConfigurableEnvironment) { processPropertySource(propertySource); }}// 3, handle @ComponentScan or @ComponentScans annotation
	@componentScan and @ComponentScans (basePackages);
	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) {
			// 3.2 Resolve the classes contained in the scanned packages configured for @ComponentScan and @ComponentScans
			// For example, basePackages = com.tiantang.study, then this step will scan the package and its subclasses and parse them into BeanDefinition
			BeanDefinitionHolder (BeanDefinitionHolder)
			Set<BeanDefinitionHolder> scannedBeanDefinitions =
					this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
			// 3.3 By scanning the classes in the com.tiantang.com package in the previous step, the beans may have added ComponentScan or ComponentScans.
			// So you need to loop once, parse, and continue parsing until there is no ComponentScan or ComponentScans on the class
			// (if statement is not entered) // (if statement is not entered)
			for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
				BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
				if (bdCand == null) {
					bdCand = holder.getBeanDefinition();
				}
				/ / in the same way, here is called ConfigurationClassUtils. CheckConfigurationClassCandidate () method to determine whether such a configuration class
				if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); }}}}// 4. Handle the bean registered with Import annotations. This step only changes the bean registered with Import to ConfigurationClass, not BeanDefinition
	// Instead, it becomes BeanDefinition in the loadBeanDefinitions() method, which then goes to BeanDefinitionMap
	// Import annotations will be covered in a separate article
	processImports(configClass, sourceClass, getImports(sourceClass), true);

	// 5. Process the configuration file introduced by the @importResource annotation
	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 methods annotated with @bean
	Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
	for (MethodMetadata methodMetadata : beanMethods) {
		configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
	}
	/ /... Omit some code
	// No superclass -> processing is complete
	return null;
}
Copy the code
2.3 this. Reader. LoadBeanDefinitions ()

This method essentially parses classes registered with @import, @bean, etc. annotations into BeanDefinition and then registers them with BeanDefinitionMap.

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
	TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
	for (ConfigurationClass configClass : configurationModel) {
    / / cycle call loadBeanDefinitionsForConfigurationClass ()loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); }}private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
	// omit some code...

	Configclass.isimported () returns true if a bean was added to the container via @import (ImportSelector)
	// The configClass importedBy property stores a ConfigurationClass, which is the class that imports the bean
	// The purpose of this step is
	if (configClass.isImported()) {
		registerBeanDefinitionForImportedConfigurationClass(configClass);
	}
	// Determine if the current bean has @definitionmap methods. If so, put beans generated by these methods in a BeanDefinitionMap
	for (BeanMethod beanMethod : configClass.getBeanMethods()) {
		loadBeanDefinitionsForBeanMethod(beanMethod);
	}
	loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
	// If there is an @import annotation on the bean, And the import is an implementation of a ImportBeanDefinitionRegistrar interface, is performing ImportBeanDefinitionRegistrar registerBeanDefinitions () method
	loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
Copy the code

3. PostProcessBeanFactory () method

This method is used to intervene in the BeanFactory creation process. Two main things are done: (1) CGLIB proxies for classes annotated with @Configuration. (2) to add a rear processor ImportAwareBeanPostProcessor Spring.

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	int factoryId = System.identityHashCode(beanFactory);
	if (this.factoriesPostProcessed.contains(factoryId)) {
		throw new IllegalStateException(
				"postProcessBeanFactory already called on this post-processor against " + beanFactory);
	}
	this.factoriesPostProcessed.add(factoryId);
	// The following if statement is not entered because BeanFactoryPostProcessor, Will perform BeanDefinitionRegistryPostProcessor postProcessorBeanDefinitionRegistry () method
	/ / but when performing postProcessorBeanDefinitionRegistry method, will call processConfigBeanDefinitions method, with the postProcessorBeanFactory () method of execution logic is the same
	/ / postProcessorBeanFactory () method will call processConfigBeanDefinitions method, in order to avoid repeat, so before the execution method will Mr Into an id, the id into a set of, every time before execution
	// Check whether the ID exists first, so at this point, the if statement is never entered
	if (!this.registriesPostProcessed.contains(factoryId)) {
		// BeanDefinitionRegistryPostProcessor hook apparently not supported...
		// Simply call processConfigurationClasses lazily at this point then.
		// This method will not be executed here
		processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
	}
	// Cglib proxy for the @configuration annotated Configuration class
	enhanceConfigurationClasses(beanFactory);
	// Add a BeanPostProcessor post-processor
	beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
Copy the code
3.1 CGLIB enhances the Configuration class
  • Using enhanceConfigurationClasses (the beanFactory) method to enhance the Configuration class, use additional to create the dynamic proxy
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
	// Save some code...
	ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
	for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
		// Save some code...
        
        / / call ConfigurationClassEnhancer. Enhance () method to create a classClass<? > enhancedClass = enhancer.enhance(configClass,this.beanClassLoader);
		// Save some code...}}Copy the code
  • ConfigurationClassEnhancer. Enhance () method
publicClass<? > enhance(Class<? > configClass,@Nullable ClassLoader classLoader) {
	// omit some code...
    // newEnHancer()Class<? > enhancedClass = createClass(newEnhancer(configClass, classLoader));// omit some code...
	return enhancedClass;
}
Copy the code
  • ConfigurationClassEnhancer. NewEnhancer () method
private Enhancer newEnhancer(Class
        configSuperClass, @Nullable ClassLoader classLoader) {
	Enhancer enhancer = new Enhancer();
    // CGLIB's dynamic proxy is based on inheritance
	enhancer.setSuperclass(configSuperClass);
    // Sets a parent interface for the newly created proxy object
	enhancer.setInterfaces(newClass<? >[] {EnhancedConfiguration.class}); enhancer.setUseFactory(false);
	enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
	enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
	// Add two methodInterceptors. (BeanMethodInterceptor and BeanFactoryAwareMethodInterceptor)
	// From the names of the two classes, you can guess that the former is to enhance the @bean annotated method and the latter is to enhance the beanFactory property of the proxy object
	// How can methods be enhanced for propped objects? This is done through the MethodInterceptor interceptor
	// Similar to the interceptor in SpringMVC, the interceptor is passed each time a request is executed.
	Also, adding a MethodInterceptor means that every time a method on an object is proxyed, the method in the MethodInterceptor is passed first
	enhancer.setCallbackFilter(CALLBACK_FILTER);
	enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
	return enhancer;
}
Copy the code
  • CGLIBCreating dynamic proxies is implemented based on inheritance (JDK dynamic proxies are implemented based on interfaces), soenhancer.setSupperclass(configSuperClass)This line of code sets the parent class for the generated proxy object and implements the generated proxy objectEnhancedConfiguration.classInterface, which is implemented for the purpose ofConfigurationClass performs the associated BeanPostProcessor during instantiation and initialization.
  • For example, in executingImportAwareBeanPostProcessorWith the rear processor,postProcessPropertyValues()Method, will be rightEnhancedConfigurationClass to set a property forEnhancedConfigurationThe implementation classbeanfactoryAttribute assignment
3.2 add ImportAwareBeanPostProcessor post processor
  • ConfigurationClassPostProcessorOf the classpostProcessBeanFactory()Method adds a Bean post-handler to the Spring container at the end:ImportAwareBeanPostProcessorThe Bean post-processor is ultimately executed during Bean instantiation and initialization, participating in Bean creation. The post-processor has been analyzed from the source code abovepostProcessPropertyValues()Method, whose function isEnhanceConfigurationClass assigned to the beanFactory property.
  • ImportAwareBeanPostProcessorcode
private static class ImportAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {

	private final BeanFactory beanFactory;

	public ImportAwareBeanPostProcessor(BeanFactory beanFactory) {
		this.beanFactory = beanFactory;
	}

	@Override
	public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) {
            // Set the beanFactory property for the proxy class that implements the EnhancedConfiguration interface when enhanced by CGLIB
		if (bean instanceof EnhancedConfiguration) {
			((EnhancedConfiguration) bean).setBeanFactory(this.beanFactory);
		}
		return pvs;
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) {
		if (bean instanceof ImportAware) {
			ImportRegistry ir = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class);
			AnnotationMetadata importingClass = ir.getImportingClassFor(bean.getClass().getSuperclass().getName());
			if(importingClass ! =null) { ((ImportAware) bean).setImportMetadata(importingClass); }}returnbean; }}Copy the code

4. To summarize

  • This article mainly analyzes the ConfigurationClassPostProcessor class, Because the class implements the spring BeanFactoryPostProcessor interface and BeanDefinitionRegistryPostProcessor interface, So will rewrite postProcessBeanDefinitionRegistry () method and postProcessBeanFactory () method.
  • inpostProcessBeanDefinitionRegistry()The @ComponentScan / @ComponentScans / @ComponentScans / @ComponentScans / @ComponentScans / @ComponentScans / @ComponentScans / @ComponentScans / @ComponentScans / @ComponentScans / @ComponentScans / And beans registered with the @import annotation and configured in the configuration file imported with the @importResource annotation. In postProcessBeanDefinitionRegistry () method, through the analysis of the source code for two very important ways:ConfigurationClassParser.parse()andthis.reader.loadBeanDefinitions()
  • inpostProcessBeanFactory()In this method, CGLIB is used to add@ConfigurationAnnotated classes are enhanced by creating dynamic proxies. Finally, a Bean post-handler is added to the Spring container:ImportAwareBeanPostProcessor

5. Flaws in this article

Note in advance, behind these pits will be published separately to explain the wechat public number, interested friends can first study their own.

  • (1) A configuration class must be added@ConfigurationComments? Can’t Spring parse without adding? For example:
@ComponentScan("com.tiantang.study")
public class AppConfig {}public class MainApplication {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = newAnnotationConfigApplicationContext(AppConfig.class); }}Copy the code
  • (2) Why should classes with @Configuration annotations be enhanced by CGLIB?
  • (3) Combined with (2), look at the code below, should be printed several timesInjection of UserDao? Once or twice? What about removing @Configuration?

In the following code, the AppConfig class adds the Configuration annotation, registers the two beans with the @bean annotation, and finally calls the userDao() method in the orderDao() method.

@Configuration
public class AppConfig {

	@Bean
	public UserDao userDao(a){
		// How many times will it be printed?
		System.out.println("Injection of UserDao");
		return new UserDao();
	}

	@Bean
	public OrderDao orderDao(a){
		Call the userDao() method here
		userDao();
		return newOrderDao(); }}// Start the container
public class MainApplication {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = newAnnotationConfigApplicationContext(AppConfig.class); }}Copy the code

Scan the qr code below or search the wechat official account, cainiao Feiyafei, you can follow the wechat official account, read more Spring source code analysis articles