Spring series - 2.2 ConfigurationClassPostProcessor

Posted by Sxooter on Fri, 28 Jan 2022 13:17:12 +0100

Spring version: spring 5.2.9 BUILD-SNAPSHOT

Modified part of the source code, but does not affect the main process

summary

ConfigurationClassPostProcessor is a post processor of BeanFactory, which implements the BeanDefinitionRegistryPostProcessor interface and also has the ability of BeanFactory postprocessor. It is used for parsing related configuration class annotations during Spring application startup.

Parsing annotation

1.@Bean

2.@Import

3.@ComponentScan/@ComponentScans

4.@ImportResource

5.@PropertySource

Inheritance diagram

From the figure above, we can see that it implements BeanDefinitionRegistryPostProcessor and PriorityOrdered.

Registration time

When or is used in the configuration file, the beanfactoryprocessor will be registered.
so Spring series - 1.3 local xml configuration parsing For the part of label resolution in, obeanefinitionparserdelegate #parsecustomelement().
Finally, the relevant registration is carried out through AnnotationConfigUtils#registerAnnotationConfigProcessors().

if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
	RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
	def.setSource(source);
	// Register BeanDefinition in the registry
	beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}

Call timing

ConfigurationClassPostProcessor implements both the method postProcessBeanDefinitionRegistry defined by BeanDefinitionRegistryPostProcessor and the method postProcessBeanFactory defined by interface beanfactory postprocessor,
Therefore, when the Spring container is started, the invokebeanfactoryprocessors () method will be used to perform the related post processor calls. See Spring series - 2.1 beanfactoryprocessor

postProcessBeanDefinitionRegistry()

/**
 * Locate, load, parse and register relevant annotations
 *
 * Derive further bean definitions from the configuration classes in the registry.
 */
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	// The hashcode value is generated according to the corresponding registry object. This object will only operate once. If it has been handled before, an exception will be thrown
	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);
	}
	// Put the id value of the registry object to be processed immediately into the processed collection object
	this.registriesPostProcessed.add(registryId);
	// Handle the bean definition information of the configuration class
	processConfigBeanDefinitions(registry);
}

processConfigBeanDefinitions

	/**
	 * Build and verify whether a class is modified by @ Configuration, and do relevant parsing work
	 *
	 * If you understand this method clearly, then the automatic assembly principle of springboot is clear
	 *
	 * Build and validate a configuration model based on the registry of
	 * {@link Configuration} classes.
	 */
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		// Create a collection of objects to store BeanDefinitionHolder
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		// The current registry is DefaultListableBeanFactory, which obtains the beanName of all registered beandefinitions
		String[] candidateNames = registry.getBeanDefinitionNames();

		// Traverse the names of all beandefinitions to be processed and filter the corresponding beandefinitions (modified by annotations)
		for (String beanName : candidateNames) {
			// Gets the BeanDefinition object with the specified name
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			// If the configurationClass attribute in beanDefinition is not equal to null, it means that it has been processed and the log information is output
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			// Judge whether the current BeanDefinition is a configuration class, and set the property of BeanDefinition to lite or full. The property value is set here for subsequent calls
			// full if proxyBeanMethods proxy configured by Configuration is true
			// If @ Bean, @ Component, @ ComponentScan, @ Import, @ ImportResource annotations are added, it is set to lite
			// If the configuration class is annotated with @ order annotation, set the order property value of BeanDefinition
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				// Add to the corresponding collection object
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		// Return immediately if no @Configuration classes were found
		// If no configuration class is found, return directly
		if (configCandidates.isEmpty()) {
			return;
		}

		// Sort by previously determined @Order value, if applicable
		// If applicable, sort according to the previously determined value of @ Order
		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
		// Judge whether the current type is SingletonBeanRegistry type
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			// Cast of type
			sbr = (SingletonBeanRegistry) registry;
			// Determine whether there is a custom beanName generator
			if (!this.localBeanNameGeneratorSet) {
				// Get custom beanName generator
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
						AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
				// If there is a custom naming generation policy
 				if (generator != null) {
					//Set the beanName generation policy for component scanning
					this.componentScanBeanNameGenerator = generator;
					// Set import bean name generation policy
					this.importBeanNameGenerator = generator;
				}
			}
		}

		// If the environment object is equal to null, the new environment object is recreated
		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

		// Parse each @Configuration class
		// Instantiate the ConfigurationClassParser class and initialize relevant parameters to complete the parsing of the configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		// Create two collection objects,
		// Store related BeanDefinitionHolder objects
		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		// Store all bean s under the scanning package
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			// Resolve BeanDefinition with @ Controller, @ Import, @ ImportResource, @ ComponentScan, @ ComponentScans, @ Bean
			parser.parse(candidates);
			// Verify the parsed Configuration class. 1. The Configuration class cannot be final. 2. The @ Bean modified method must be rewritable to support CGLIB
			parser.validate();

			// Get all beans, including scanned bean objects and @ Import imported bean objects
			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			// Clear the configuration classes that have been resolved and processed
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			// Judge whether the reader is empty. If it is empty, create a reader with a fully populated ConfigurationClass instance
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			// The core method converts the fully filled ConfigurationClass instance into BeanDefinition and registers it in the IOC container
			this.reader.loadBeanDefinitions(configClasses);
			// Add to the processed collection
			alreadyParsed.addAll(configClasses);

			candidates.clear();
			// Judge registry here getBeanDefinitionCount() > candidateNames. The purpose of length is to know the reader Did you add a new BeanDefinition to BeanDefinitionMap in loadbean definitions (configclasses)
			// In fact, it depends on the configuration class (for example, AppConfig class will add beans to BeanDefinitionMap)
			// If yes, register Getbeandefinitioncount() will be greater than candidatenames length
			// In this way, you need to traverse the newly added BeanDefinition again and judge whether these beans have been resolved. If not, you need to resolve again
			// The bean added to the container by the AppConfig class here is actually in the parser Parse () has been fully resolved
			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 unresolved classes, add them to candidates. If candidates are not empty, they will 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(new BeanDefinitionHolder(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) {
			// Clear cache in externally provided MetadataReaderFactory; this is a no-op
			// for a shared cache since it'll be cleared by the ApplicationContext.
			((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
		}
	}
  • Core process

Use ConfigurationClassParser to discover all configuration classes, and use ConfigurationClassBeanDefinitionReader to register all bean definitions in the discovered configuration classes. The condition for ending execution is that all configuration classes are found and processed, and the corresponding BeanDefinition is registered to the container.

parser.parse(candidates)

// Resolve BeanDefinition with @ Controller, @ Import, @ ImportResource, @ ComponentScan, @ ComponentScans, @ Bean
    parser.parse(candidates);
	public void parse(Set<BeanDefinitionHolder> configCandidates) {
		// Loop through configCandidates
		for (BeanDefinitionHolder holder : configCandidates) {
			// Get BeanDefinition
			BeanDefinition bd = holder.getBeanDefinition();
			// Depending on the type of BeanDefinition, different overloaded methods of parse are called. In fact, the processConfigurationClass() method is called in the end
			try {
				// annotation type 
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				// With class object
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}

		// Execute DeferredImportSelector found
		// DeferredImportSelector is a subclass of ImportSelector
		// ImportSelector is designed to have the same effect as @ Import annotation, but the class that implements ImportSelector can conditionally decide to Import some configurations
		// DeferredImportSelector is designed to be processed only after all other configuration classes have been processed
		this.deferredImportSelectorHandler.process();
	}

processConfigurationClass

	protected void  processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
		// Determine whether to skip parsing
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}

		// When entering for the first time, the size of configurationClass is 0, and existingClass must be null. Handle the repeated import of configuration here
		// If the same configuration class is processed twice and both belong to the imported class, the imported class is merged and returned. If the configuration class is not imported, the old configuration class is removed and the new configuration class is used
		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) {
			if (configClass.isImported()) {
				if (existingClass.isImported()) {
					// If the configuration class configclass to be processed already exists in the configuration class record that has been analyzed and processed, merge the importBy attribute of both
					existingClass.mergeImportedBy(configClass);
				}
				// Otherwise ignore new imported config class; existing non-imported class overrides it.
				return;
			}
			else {
				// Explicit bean definition found, probably replacing an import.
				// Let's remove the old one and go with the new one.
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}

		// Recursively process the configuration class and its superclass hierarchy.

		// Handle the configuration class. Since the configuration class may have a parent class (except if the full class name of the parent class starts with java), it is necessary to change configClass into sourceClass for resolution, and then return the parent class of sourceClass.
		// If the parent class is empty at this time, it will not be resolved in a while loop. If the parent class is not empty, it will be resolved in a loop
		// The meaning of SourceClass: a simple wrapper class is designed to handle annotated classes in a unified way, no matter how they are loaded
		// If you can't understand it, you can treat it as a black box, which will not affect the mainstream process of looking at the spring source code
		SourceClass sourceClass = asSourceClass(configClass, filter);
		do {
			// Parsing various annotations
			sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
		}
		while (sourceClass != null);

		// Store the parsed configuration class so that you can get the value when you return to the parse method
		this.configurationClasses.put(configClass, configClass);
	}

doProcessConfigurationClass()

	protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {
		// @Configuration inherits @ Component
		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			// The internal class is processed recursively, because the internal class is also a configuration class. There is a @ configuration annotation on the configuration class, which inherits @ Component. if it is judged to be true, call the processMemberClasses method to recursively resolve the internal class in the configuration class
			processMemberClasses(configClass, sourceClass, filter);
		}

		// Process any @PropertySource annotations
		// If the @ PropertySource annotation is added to the configuration class, the properties file is parsed and loaded, and the properties are added to the spring context
		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
		// Process @ ComponentScan or @ ComponentScans annotations and convert all bean s under the scan package into populated ConfigurationClass
		// Here is to load the custom bean s into the IOC container, because the scanned classes may also be annotated with @ ComponentScan and @ ComponentScans, so recursive parsing is required
		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
				// Resolve the classes contained in the scanned packages configured by @ ComponentScan and @ ComponentScans
				// For example, basepackages = com Mashibing. In this step, the class es under this package and its sub packages will be scanned, and then parsed into BeanDefinition
				// (BeanDefinition can be understood as equivalent to BeanDefinitionHolder)
				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
				// Scan package com.com through the previous step Mashibing. The bean s that may be scanned may also be annotated with ComponentScan or ComponentScans
				//Therefore, you need to cycle through it once, parse it, and continue parsing until there are no ComponentScan and ComponentScans on the parsed class
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					// Judge whether it is a configuration class and set the full or lite attribute
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
						// Parsing by recursive method
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

		// Process any @Import annotations
		// Process @ Import annotation
		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

		// Process any @ImportResource annotations
		// Handle the @ ImportResource annotation and import the spring configuration file
		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 the method annotated with @ Bean, convert the @ Bean method into a BeanMethod object, and save it in the collection
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

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

		// Process superclass, if any
		// Resolve the parent class. If the resolved configuration class inherits a class, the parent class of the configuration class will also be resolved
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!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;
	}
  • Core process

(1) Handle the internal class. If the internal class is also a configuration class (judge whether a class is a configuration class through ConfigurationClassUtils.checkConfigurationClassCandidate()).
(2) The @ PropertySource annotation is added to process the property resource file.
(3) First, parse the @ ComponentScan and @ ComponentScans annotations on the class, and then scan all classes that need to be managed by Spring by using ASM Technology (ASM technology is a technology for operating bytecode, and interested friends can go online to learn about it) according to the configured scanning package path. Because @ ComponentScan and @ ComponentScans annotations may also be added to the scanned classes, Therefore, recursive parsing is required until all classes annotated with these two annotations are parsed.
(4) Process @ Import annotation. Through the @ Import annotation, there are three ways to register a Bean into the Spring container.
(5) Handle the @ ImportResource annotation and parse the configuration file.
(6) Handle methods annotated with @ Bean.
(7) Process the default methods of the interface through processInterfaces(). Starting from JDK8, the methods in the interface can have their own default implementation. Therefore, if the methods in the interface are also annotated with @ Bean, they also need to be resolved. (rarely used)
(8) Resolve the parent class. If the resolved configuration class inherits a class, the parent class of the configuration class will also be resolved doprocessconfigurationclass() (the parent class is an exception to the built-in class in JDK, that is, the full class name starts with java).

this.reader.loadBeanDefinitions(configClasses)

// The core method converts the fully filled ConfigurationClass instance into BeanDefinition and registers it in the IOC container
	this.reader.loadBeanDefinitions(configClasses);
	/**
	 * Read {@code configurationModel}, registering bean definitions
	 * with the registry based on its contents.
	 */
	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
		for (ConfigurationClass configClass : configurationModel) {
			// Loop through the loadBeanDefinitionsForConfigurationClass method
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}

	/**
	 * Read a separate ConfigurationClass and register the bean itself (@ Import introduced ordinary class) or the bean method of @ Configuration configuration class
	 *
	 * Read a particular {@link ConfigurationClass}, registering bean definitions
	 * for the class itself and all of its {@link Bean} methods.
	 */
	private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

		if (trackedConditionEvaluator.shouldSkip(configClass)) {
			String beanName = configClass.getBeanName();
			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
				this.registry.removeBeanDefinition(beanName);
			}
			this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
			return;
		}
		// If a bean is added to the container through @ Import(ImportSelector), then configclass Isimported() returns true
		// In addition, the importedBy attribute of configClass stores ConfigurationClass, which is the class to import bean s
		if (configClass.isImported()) {
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
		// Judge whether the current Bean contains @ Bean annotation methods. If so, put the beans generated by these methods into the BeanDefinitionMap
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}

		// Inject the resources introduced by @ ImportResource into the IOC container
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
		// If the @ import annotation exists on the bean and the import is an interface that implements ImportBeanDefinitionRegistrar, execute the registerBeanDefinitions() method of ImportBeanDefinitionRegistrar
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}

registerBeanDefinitionForImportedConfigurationClass()

	private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
		AnnotationMetadata metadata = configClass.getMetadata();
		AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);

		ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
		configBeanDef.setScope(scopeMetadata.getScopeName());
		String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
		AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);

		BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
		definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
		this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
		configClass.setBeanName(configBeanName);

		if (logger.isTraceEnabled()) {
			logger.trace("Registered bean definition for imported class '" + configBeanName + "'");
		}
	}

loadBeanDefinitionsForBeanMethod()

	/**
	 * Take out the method metadata of Bean annotation for analysis, analyze @ Bean annotation, whether there is alias or conflict with xml configuration, and package it into a configurationClassBeanDefinition
	 * Then set the factory method name, get the properties of the bean annotation, set the initialization method, destruction method, whether to assemble automatically, whether to need an agent, etc
	 *
	 * Read the given {@link BeanMethod}, registering bean definitions
	 * with the BeanDefinitionRegistry based on its contents.
	 */
	@SuppressWarnings("deprecation")  // for RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE
	private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
		ConfigurationClass configClass = beanMethod.getConfigurationClass();
		// Get method metadata
		MethodMetadata metadata = beanMethod.getMetadata();
		// Get method name
		String methodName = metadata.getMethodName();

		// Do we need to mark the bean as skipped by its condition?
		if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
			configClass.skippedBeanMethods.add(methodName);
			return;
		}
		if (configClass.skippedBeanMethods.contains(methodName)) {
			return;
		}

		// Get the properties of the bean annotation
		AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
		Assert.state(bean != null, "No @Bean annotation attributes");

		// Consider name and any aliases
		// Get alias
		List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
		String beanName = (!names.isEmpty() ? names.remove(0) : methodName);

		// Register aliases even when overridden
		for (String alias : names) {
			// Register the remaining aliases
			this.registry.registerAlias(beanName, alias);
		}

		// Has this effectively been overridden before (e.g. via XML)?
		// Is there a bean definition with the same name
		if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
			if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {
				throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
						beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() +
						"' clashes with bean name for containing configuration class; please make those names unique!");
			}
			return;
		}

		// Encapsulated as ConfigurationClassBeanDefinition, indicating that it is from the bean definition in the configuration class
		ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
		// Set source class
		beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));

		// Determine whether it is static and set BeanClass
		if (metadata.isStatic()) {
			// static @Bean method
			if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
				beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
			}
			else {
				beanDef.setBeanClassName(configClass.getMetadata().getClassName());
			}
			beanDef.setUniqueFactoryMethodName(methodName);
		}
		else {
			// instance @Bean method
			// Set factory name
			beanDef.setFactoryBeanName(configClass.getBeanName());
			beanDef.setUniqueFactoryMethodName(methodName);
		}

		// If the method metadata is standard method metadata, set the factory method for parsing
		if (metadata instanceof StandardMethodMetadata) {
			beanDef.setResolvedFactoryMethod(((StandardMethodMetadata) metadata).getIntrospectedMethod());
		}

		// Set the custom assembly mode. The default is constructor
		beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
		// Set skip property check
		beanDef.setAttribute(org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.
				SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);

		// Handle general annotations, and there may be automatic assembly annotations in the annotations
		AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);

		// Get auto assembly enumeration information
		Autowire autowire = bean.getEnum("autowire");
		if (autowire.isAutowire()) {
			//If it is automatic assembly, that is, BY_NAME or BY_TYPE, set the automatic assembly mode again
			beanDef.setAutowireMode(autowire.value());
		}

		// Auto assembly candidate. The default value is true
		boolean autowireCandidate = bean.getBoolean("autowireCandidate");
		if (!autowireCandidate) {
			beanDef.setAutowireCandidate(false);
		}

		// Initializing methods @ PostConstruct and @ PreDestory or XML or InitializingBean and DisposableBean interfaces
		String initMethodName = bean.getString("initMethod");
		if (StringUtils.hasText(initMethodName)) {
			beanDef.setInitMethodName(initMethodName);
		}

		// Destruction method
		String destroyMethodName = bean.getString("destroyMethod");
		beanDef.setDestroyMethodName(destroyMethodName);

		// Consider scoping
		// Processing scope
		ScopedProxyMode proxyMode = ScopedProxyMode.NO;
		AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
		if (attributes != null) {
			beanDef.setScope(attributes.getString("value"));
			proxyMode = attributes.getEnum("proxyMode");
			if (proxyMode == ScopedProxyMode.DEFAULT) {
				proxyMode = ScopedProxyMode.NO;
			}
		}

		// Replace the original bean definition with the target one, if necessary
		BeanDefinition beanDefToRegister = beanDef;
		// If the scope is not no, use the proxy
		if (proxyMode != ScopedProxyMode.NO) {
			BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
					new BeanDefinitionHolder(beanDef, beanName), this.registry,
					proxyMode == ScopedProxyMode.TARGET_CLASS);
			beanDefToRegister = new ConfigurationClassBeanDefinition(
					(RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata, beanName);
		}

		if (logger.isTraceEnabled()) {
			logger.trace(String.format("Registering bean definition for @Bean method %s.%s()",
					configClass.getMetadata().getClassName(), beanName));
		}
		this.registry.registerBeanDefinition(beanName, beanDefToRegister);
	}

postProcessBeanFactory

	/**
	 * Add CGLIB enhanced processing and ImportAwareBeanPostProcessor post-processing classes
	 *
	 * Prepare the Configuration classes for servicing bean requests at runtime
	 * by replacing them with CGLIB-enhanced subclasses.
	 */
	@Override
	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);
		if (!this.registriesPostProcessed.contains(factoryId)) {
			// BeanDefinitionRegistryPostProcessor hook apparently not supported...
			// Simply call processConfigurationClasses lazily at this point then.
			processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
		}

		enhanceConfigurationClasses(beanFactory);
		beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
	}
  • Core process
    1. CGLIB proxy the class annotated with @ Configuration.

2. Add a postprocessor ImportAwareBeanPostProcessor to Spring.

enhanceConfigurationClasses

	public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
		Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
		for (String beanName : beanFactory.getBeanDefinitionNames()) {
			BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
			Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
			MethodMetadata methodMetadata = null;
			if (beanDef instanceof AnnotatedBeanDefinition) {
				methodMetadata = ((AnnotatedBeanDefinition) beanDef).getFactoryMethodMetadata();
			}
			if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {
				// Configuration class (full or lite) or a configuration-derived @Bean method
				// -> resolve bean class at this point...
				AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;
				if (!abd.hasBeanClass()) {
					try {
						abd.resolveBeanClass(this.beanClassLoader);
					}
					catch (Throwable ex) {
						throw new IllegalStateException(
								"Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
					}
				}
			}
			if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
				if (!(beanDef instanceof AbstractBeanDefinition)) {
					throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
							beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
				}
				else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
					logger.info("Cannot enhance @Configuration bean definition '" + beanName +
							"' since its singleton instance has been created too early. The typical cause " +
							"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
							"return type: Consider declaring such methods as 'static'.");
				}
				configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
			}
		}
		if (configBeanDefs.isEmpty()) {
			// nothing to enhance -> return immediately
			return;
		}

		ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
		for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
			AbstractBeanDefinition beanDef = entry.getValue();
			// If a @Configuration class gets proxied, always proxy the target class
			beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
			// Set enhanced subclass of the user-specified bean class
			Class<?> configClass = beanDef.getBeanClass();
                       // Call configurationclassenhancer The enhance () method creates an enhancement class
			Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
			if (configClass != enhancedClass) {
				if (logger.isTraceEnabled()) {
					logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
							"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
				}
				beanDef.setBeanClass(enhancedClass);
			}
		}
	}

summary

  1. Function of ConfigurationClassPostProcessor class: this class implements the beanfactory postprocessor interface and BeanDefinitionRegistryPostProcessor interface, so it will override the postProcessBeanDefinitionRegistry() method and postProcessBeanFactory() method.

  2. In the postProcessBeanDefinitionRegistry() method, the class annotated with @ Configuration is resolved. At the same time, the beans scanned by @ ComponentScan and @ ComponentScans will also be resolved. The beans registered by the method annotated with @ Bean and the beans configured in the Configuration file imported through @ Import annotation and @ ImportResource annotation will also be resolved. In the postprocessbeandefinitionregistry () method, two very important methods are analyzed through the source code: configurationclassparser Parse() and this reader. loadBeanDefinitions().

  3. In the postProcessBeanFactory() method, CGLIB will be used to create a dynamic proxy for the class annotated with @ Configuration for enhancement. Finally, a Bean postprocessor, ImportAwareBeanPostProcessor, will be added to the spring container.

Topics: Spring source code analysis