preface
In the previous article mentioned ConfigurationClassPostProcessor, see below ConfigurationClassPostProcessor acted as what kind of role in the spring.
The body of the
Source code version:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.28..RELEASE</version>
</dependency>
Copy the code
annotation
The first thing to look at is the class comment:
/ * * * {@link BeanFactoryPostProcessor} used for bootstrapping processing of
* {@link Configuration @Configuration} classes. * are used to bootstrap classes that handle Configuration types (that is, classes that handle Configuration, such as by@Configuration* * <p>Registered by default when using {@code <context:annotation-config/>} or
* {@code<context:component-scan/>}. Otherwise, may be declared manually as * with any other BeanFactoryPostProcessor. * Using <context:annotation-config/> or <context:component-scan/> is registered (to the Spring container) by default, and vice versa, It can also be declared manually like any other BeanFactoryPostProcessor. * * <p>This post processor is priority-ordered as it is important that any * {@link Bean} methods declared in {@code @Configuration} classes have
* their corresponding bean definitions registered before any other
* {@linkBeanFactoryPostProcessor} executes. * This post-processing is prioritized and, importantly, executes in@ConfigurationSome Bean methods declared in the labeled class (configuration class), * the Bean information corresponding to these Bean methods is registered before some other BeanFactoryPostProcessor executes. (for example, an implementation class for customizing BeanFactoryPostProcessor) */
Copy the code
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry
Following is a method of BeanDefinitionRegistryPostProcessor interface, and see this class implements this interface to do something.
/** * Derive further bean definitions from the configuration classes in the registry. */
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry); // Consistency hash code
if (this.registriesPostProcessed.contains(registryId)) { // Check whether processed exists at registriesPostProcessed
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {// Check whether processed in factoriesPostProcessed exists
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);// Add to registriesPostProcessed
processConfigBeanDefinitions(registry);
}
Copy the code
The code for this method is relatively simple, and the comments are written in the code. The main idea is to check whether the consistency ID of Registry is already in PostProcessed.
ConfigurationClassPostProcessor.processConfigBeanDefinitions
PostProcessBeanDefinitionRegistry method call processConfigBeanDefinitions method, at the end, the following is processConfigBeanDefinitions method:
/**
* Build and validate a configuration model based on the registry of
* {@link Configuration} classes.
*/
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// Define a collection to collect configuration information
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
// Get the names of all registered beans
String[] candidateNames = registry.getBeanDefinitionNames();
// Iterate through the collection to filter out the bean information belonging to the Configuration
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if(beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) ! =null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: "+ beanDef); }}else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(newBeanDefinitionHolder(beanDef, beanName)); }}// Return immediately if no @Configuration classes were found
// Check whether there is configuration information, no return
if (configCandidates.isEmpty()) {
return;
}
// Sort by previously determined @Order value, if applicable
// Sort the configuration information set in 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
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if(generator ! =null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator; }}}// Set the standardized environment
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
// Parse each @configuration class // Build a parser for the Configuration file
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// Put the configuration information into the set.
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
// Already converted
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// Parse the configuration file
parser.parse(candidates);
parser.validate(); / / verification
// Parsed configuration file
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);// Remove parsed files
// 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());
}
this.reader.loadBeanDefinitions(configClasses); // Read the configuration file
alreadyParsed.addAll(configClasses); // Add to the collection of parsed files
candidates.clear(); // Clear the cache
// Determine if the number of registered beans after parsing is greater than the number before parsing (which is almost always the case)
if (registry.getBeanDefinitionCount() > candidateNames.length) {
// The following is to determine whether there is any missing configuration information in the preceding parsing process
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) && ! 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
// Register ImportRegistry as a bean to support the ImportAware configuration class
if(sbr ! =null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
// Clear the cache
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(); }}Copy the code
The above code is also relatively simple, the comments are basically noted inside the code. The most important of these is the ConfigurationClassParser class, which by its name parses configuration classes. Let’s see how he parses the configuration class.
ConfigurationClassParser. Parse method
Begin to enter into the org. Springframework. Context. The annotation. ConfigurationClassParser# parse (),
The code for this method is also relatively simple: determine the type of the Config class and choose how to parse it. Finally they are unified call to processConfigurationClass this method
public void parse(Set<BeanDefinitionHolder> configCandidates) {
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()); }}catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); }}this.deferredImportSelectorHandler.process();
}
Copy the code
ConfigurationClassParser.processConfigurationClass
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
// This place handles @conditional annotations,
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if(existingClass ! =null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
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.
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while(sourceClass ! =null);
this.configurationClasses.put(configClass, configClass);
}
Copy the code
This method code is relatively simple, and I have no comment, there are two points, one is conditionEvaluator shouldSkip, he is mainly dealing with @ Conditional comments, here is not to look at the specific implementation. Another is doProcessConfigurationClass method, this method directly on locating the doProcessConfigurationClass method.
ConfigurationClassParser.doProcessConfigurationClass
This method is really the heart of parsing configuration files.
protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate
filter)
throws IOException {
Members of the class / / recursive processing, if there are members of the class recursive call processConfigurationClass processing
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass, filter);
}
Annotations // Process any @propertysource annotations // Process @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() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan 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 the @import annotation
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// Process any @ImportResource annotations
/ / @ ImportResource processing
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
// Process a single bean method
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
// Handle interface default methods
processInterfaces(configClass, sourceClass);
// Process superclass, if any
// Superclass processing, if any
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
returnsourceClass.getSuperClass(); }}// No superclass -> processing is complete
return null;
}
Copy the code
Here is a flowchart for parsing configuration files:
ConfigurationClassPostProcessor.postProcessBeanFactory
ConfigurationClassPostProcessor realized BeanDefinitionRegistryPostProcessor, another realization method is postProcessBeanFactory.
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Consistency hashcode
int factoryId = System.identityHashCode(beanFactory);
if (this.factoriesPostProcessed.contains(factoryId)) {// Check if it has already been called
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + beanFactory);
}
this.factoriesPostProcessed.add(factoryId);// Add it to the factory
if (!this.registriesPostProcessed.contains(factoryId)) {
/ / BeanDefinitionRegistryPostProcessor not be invoked when the call can be a lazy loading
// BeanDefinitionRegistryPostProcessor hook apparently not supported...
// Simply call processConfigurationClasses lazily at this point then.
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
}
// Enhance the configuration class
enhanceConfigurationClasses(beanFactory);
/ / register ImportAwareBeanPostProcessor
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
Copy the code
This method is the most important is the last line, registered ImportAwareBeanPostProcessor, then this ImportAwareBeanPostProcessor is stem what of, you only post his two implementation methods of code:
@Override
public PropertyValues postProcessProperties(@Nullable PropertyValues pvs, Object bean, String beanName) {
// Inject the BeanFactory before AutowiredAnnotationBeanPostProcessor's
// postProcessProperties method attempts to autowire other configuration beans.
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(ClassUtils.getUserClass(bean).getName());
if(importingClass ! =null) { ((ImportAware) bean).setImportMetadata(importingClass); }}return bean;
}
Copy the code
The postProcessProperties method handles the implementation class of the EnhancedConfiguration interface. PostProcessBeforeInitialization is processing ImportAware interface implementation class.
The last
To achieve a ConfigurationClassPostProcessor BeanDefinitionRegistryPostProcessor, PostProcessBeanDefinitionRegistry method is mainly used for processing parsing configuration information, and mainly is the registered ImportAwareBeanPostProcessor postProcessBeanFactory method.
Actually in springboot source, there are a lot of configuration, the configuration class is ConfigurationClassPostProcessor parsing.