Spring — Configure the class resolution process
Start with a small demo below
The demo is very simple, direct use of AnnotationConfigApplicationContext, passed to him the configuration class.
When parsing a configuration class, there are two big steps,
- Register this configuration class with Spring as a normal Bean.
- In the use of
ConfigurationClassPostProcessor (BeanDefinitionRegistryPostProcessor implementation class)
In thepostProcessBeanDefinitionRegistry
Method to resolve the configuration class and register the BeanDefinition resolved from the configuration class into Spring. Just catch up on the callFinishBeanFactoryInitialization method
Previously, it would have been nice to register the bean with Spring.
Register configuration classes into Spring
As you can see from the class diagram, there are three main classes that are required for registration, ClassPathBeanDefinitionScanner and AnnotatedBeanDefinitionReader polymerization in AnnotationConfigApplicationContext.
At the time of creating AnnotationConfigApplicationContext, this pair is created. In addition, still need to register, also need to create a ConditionEvaluator in AnnotatedBeanDefinitionReader (based on the analysis of @ Condition annotation has said before), for processing Condition. In addition, also called AnnotationConfigUtils. RegisterAnnotationConfigProcessors (enclosing registry); Method to register some generic annotation configuration processing as follows:
- The order of the comparator (AnnotationAwareOrderComparator)
- The autowire calculator (ContextAnnotationAutowireCandidateResolver)
- Configuration class of parsing (ConfigurationClassPostProcessor)
- The autowire parsing (AutowiredAnnotationBeanPostProcessor)
- Support JSR – 250 annotations and parsing (CommonAnnotationBeanPostProcessor)
- Jpa support (PersistenceAnnotationBeanPostProcessor)
- The support of the @ EventListener (EventListenerMethodProcessor)
- Used to create the EventListener (DefaultEventListenerFactory)
Note that these are created when creating AnnotatedBeanDefinitionReader.
For ClassPathBeanDefinitionScanner, at the time of creation, besides some common assignment operations, he also inside by useDefaultFiltersbool parameters, to control whether to add the default type filter. Specific methods in ClassPathBeanDefinitionScanner constructor of a class. ClassPathScanningCandidateComponentProvider#registerDefaultFilters().
Note: The above said those are created when creating AnnotationConfigApplicationContext, and implement the AnnotationConfigApplicationContext BeanDefinitionRegistry interface. Those two can use it to register BeanDefinition.
Is ultimately by calling AnnotatedBeanDefinitionReader# register method will Bean into the BeanFactory registration. Because of AnnotationConfigApplicationContext register method is a mutable array can be passed. The final implementation is a circular call to register the configuration class with the BeanFactory.
Methods:
The general idea: parse this configuration class as a normal bean and register it with the BeanFactory.
- Create AnnotatedGenericBeanDefinition, when creating it will pass
AnnotationMetadata.introspect(beanClass);
Get the meta information on this class (meta information includes its class information, annotation information, interface information, field information, etc.). You can see the detailsAnnotationMetadata class concrete implementation
- Since it is also a normal bean, it may have the @condition annotation, so it must be passed first
conditionEvaluator.shouldSkip(abd.getMetadata())
To judge the @condition annotation. - Handle the @scope annotation.
- Handle basic annotations for bean information, such as Lazy, Primary, DependsOn, Role, and Description.
- Handle BeanDefinitionCustomizer, this parameter can be passed
AnnotationConfigApplicationContext#registerBean
Method passed in. - Encapsulate as a BeanDefinitionHolder. Handle proxyModel parameters in scope annotations. Register with beanFactory. A little bit of caution
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry)
Method, in which the appropriate proxy object is created by scope’s proxyModel.
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
/ / create AnnotatedGenericBeanDefinition, need to pay attention to, when it was created for this class of yuan above information,
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
// 计算@condition
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
// Instance provider,
abd.setInstanceSupplier(supplier);
// Retrieve the metadata for the scope annotation
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
// Simple interest or multiple cases or something else
abd.setScope(scopeMetadata.getScopeName());
// Generate a nameString beanName = (name ! =null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
// Handle general annotation information, such as @role
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
if(qualifiers ! =null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
if (Primary.class == qualifier) {
abd.setPrimary(true);
}
else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
}
else {
abd.addQualifier(newAutowireCandidateQualifier(qualifier)); }}}if(customizers ! =null) {
for(BeanDefinitionCustomizer customizer : customizers) { customizer.customize(abd); }}//
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
// Apply the scope annotation to the proxyModel schema.
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
Copy the code
Using ConfigurationClassPostProcessor to parse the configuration class.
Method calls are in AbstractApplicationContext# refresh, this method is the core of the Spring.
After the BeanFactory ready, is called AbstractApplicationContext# invokeBeanFactoryPostProcessors method, in this method will be called all the BeanFactoryPostProcess, About this method is not all posted here, say about the general process.
-
There is a in AbstractApplicationContext beanFactoryPostProcessors properties, storage is the set of all spring BeanFactoryPostProcessor. In the meantime, Spring provides a corresponding Add method. So it’s entirely possible to add some BeanFactoryPostProcessor when you call ApplicationContext. If you don’t add it, you won’t have it in the first place because those beans are still BeanDefinitions, so you need to be compatible with this at the beginning of the method. First call beanFactoryPostProcessors set inside the bean.
-
The main idea is to call BeanFactoryPostProcessor. But there are a couple of things going on here, first of all
- Priority of Ordered and PriorityOrdered annotations.
- BeanDefinitionRegisteryPostProcess and BeanDefinitionPostProcess priority.
According to the analysis above, BeanDefinitionRegisteryPostProcess priority, PriorityOrdered high priority.
-
Note that in Spring, beans of whatever type are defined are declared. Earlier XML, now @component. Are. Except for the objects that FactoryBean creates. As such, it must be defined in Spring and declared using XML or the @Component annotation. Spring will find out.
-
BeanFactoryPostProcessor is created by following the standard Bean creation process. That is, the init methods, the Aware interface, and the destroy methods are all applied. All of the Bean definition information has already been added to the BeanFactory when the refresh method is called, but it can continue to be added until preInstantiateSingletons are called.
The problem?
What if a BeanPostProcess requires a Bean defined by itself? Will cause the bean to initialize prematurely? Can you annotate it with @autowire?
This is ok, it is available, and the bean referenced will be instantiated prematurely.
You can’t annotate @autowire; you can inject it with a constructor. In theory, go Spring standards create bean, there will be a property injection, but the @autowired annotation parsing is inside the AutowiredAnnotationBeanPostProcessor, it is a BeanPostProcessor. This is initialized after the BeanFactoryPostProcess is called. It is not allowed in BeanFactoryPostProcess.
But constructor injection is ok. Because constructor injection happens when the bean is created. At this point it is clear what arguments are required to create this object, and Spring will do the doGetBean. At this point, the dependent object is created.
postProcessBeanDefinitionRegistry
Main functions:
Parse the configuration class and register the resolved beanDefinition with SpringFactory.
This method will eventually call to ConfigurationClassPostProcessor# processConfigBeanDefinitions method. Let’s start parsing in this one. Here comes the main point of this article. Here is the code for analysis.
Methods:
It can be divided into three steps
The first is to find the Configuration class annotated by the @Configuration annotation. (The Metadata must be traversed, using the above mentioned Metadata to obtain the meta information of the annotation, retrieve.)
Second: The configuration class is resolved using the ConfigurationClassParser parser.
Third: Use beanDefintionReader to read the configuration class, change it to BeanDefinition, and register it with Spring.
See the code comment for details.
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
// Start traversal
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
// Check for CONFIGURATION_CLASS_ATTRIBUTE. The CONFIGURATION_CLASS_ATTRIBUTE attribute is a bit special and will be used later to enhance class configuration.
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 if it is a class modified by the Configuration annotation. See this metadataReaderFactory. It must be checked by Metadata
// Also set some data for beanDefinition (according to proxyBeanMethods and Order annotations)
BeanFactoryPostProcessor, BeanPostProcessor, AopInfrastructureBean, and EventListenerFactory beans all return false
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(newBeanDefinitionHolder(beanDef, beanName)); }}// Return if no class is configured.
if (configCandidates.isEmpty()) {
return;
}
// Sort in the natural order
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// Get the BeanNameGenerator.
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; }}}if (this.environment == null) {
this.environment = new StandardEnvironment();
}
// Create a parser to count interest configuration classes.
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// The initial set of traversals
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
// The final result
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// Parse and validate
parser.parse(candidates);
ConfigurationClass object, loop through, @configuration proxyBeanMethods should be checked if true.
// If validation is required, the configuration class cannot be final, and the @bean method cannot be overridden.
parser.validate();
// finally, the parsed configurationClasses are encapsulated into configurationClasses of ConfigurationClassParser#.
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// do the loading.
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
// From the resolved configuration, parse the Beandefinition
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
// There is a little tip here. If there are now more than before, it means that the configuration class really registered some beans to the BeanFactory.
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());
}
for (String candidateName : newCandidateNames) {
if(! oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName);// If the new derived bean is a configuration class, parsing continues. Wait until the candidates are empty
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && ! alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(newBeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; }}while(! candidates.isEmpty());// Register two beans.
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
The logic of the body is already shown in the code above. The above two key classes, ConfigurationClassParser (parsing configuration class), ConfigurationClassBeanDefinitionReader (BeanDefinition) from the configuration class registration.
ConfigurationClassParser
Parse analysis
Method into is a set of candidate configuration class, cycle start parsing, in this way through different parameter types most always call to ConfigurationClassParser# processConfigurationClass method. The ConfigurationClass is encapsulated as a ConfigurationClass object, as follows:
Its properties, in fact, represent the configuration of the class’s various available information. This is important, in the process of parsing, is to put the parsed things in the corresponding properties. This concept runs through configuration class resolution.
Let’s look at the processConfiguClass action. This method is an important one and will be called many times. As mentioned earlier, when parsing a ConfigurationClass, it is encapsulated as a ConfigurationClass object.
- Since the configuration class also has the @condition annotation on it, you need to judge first.
- Because this class parses the configuration class, and this method is called multiple times. Multiple calls means you can do a cache. configurationClasses
- The ConfigurationClass is again encapsulated as SourceClass.
- The recursion starts parsing, parsing its superclass.
- Finally, it goes into the cache.
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
// Evaluate the @condition annotation
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
// Check the cache
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); }}// Resolve the parent class recursively
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while(sourceClass ! =null);
// Place the cache.
this.configurationClasses.put(configClass, configClass);
}
Copy the code
The problem?
Why change ConfigurationClass to SourceClass again?
ConfigClass is the final representation of a configuration class, but SourceClass is only used in the middle of parsing a configuration class. Furthermore, this representation does not care how SourceClass is actually loaded and can be used for unified management
Here’s the do method, the real way of doing things.
Notice that the last parameter of this method that I haven’t explained yet,
filter
The default is to look like this. If the filter is used to build a SourceClass Object, it will return an Object if tired. It’s not a real bean. That is, the bean will use an Object code, later in sourceClass
(className.startsWith("java.lang.annotation.") || className.startsWith("org.springframework.stereotype.")); Copy the code
This method is really starting to parse configuration classes. Think about what annotations are usually available when you use configuration classes. Look at the ConfigurationClass member variables mentioned earlier. You get the whole picture.
Look at the notes
protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate
filter)
throws IOException {
// If the configuration class is a method decorated by Component. Will parse its inner class, because the inner class might also be a configuration class
/ / so have to go again in the above said the important method of processConfigurationClass method, the internal classes encapsulate a ConfigurationClass
/ / to resolve
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass, filter);
}
// Parse the PropertySources annotation. There's no recursive parsing here, so what this annotation does is load the properties file, add it to the environment,
// We pass in the ConfigurableEnvironment object, and all we need to do is resolve the location of the file
// Form a CompositePropertySource and add it to MutablePropertySources, which is a list.
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"); }}ComponentScan () {// ComponentScan () {// ComponentScan ()
ConditionEvaluator (Condition); // conditionEvaluator (Condition); // conditionEvaluator (Condition)
// componentScanParser scans beans from specified packages and encapsulates them as BeanDefinitionHolders.
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();
}
// Check whether the beans are configured classes, because no one knows whether the beans are configured classes.
/ / now that is is a configuration class, is called directly above said parsing configuration method of a class, called again (processConfigurationClass)
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); }}}}// Parse @import annotations.
// Use @import as an annotation. // Use @import as an annotation.
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// Parse @importResource annotation. This annotation imports configuration resources, such as XML configuration beans.
// Call it a map. The key of the map is the name of the configuration file. V is beanDefinitionReader.
/ / if it is groovy files will be through GroovyBeanDefinitionReader, the rest are XmlBeanDefinitionReader.
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); }}// parse the @bean method, as long as it is AnnotationMetadata#getAnnotatedMethods.
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Parse the interface method with the default modifier, which is directly to retrieve the interface set, edit the set, and retrieve the @bean method in the interface, and this method is not abstract
// There is a recursive call,
processInterfaces(configClass, sourceClass);
// Parse its parent class
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
// The parent class cannot start with Java, that is, it is not suitable for objects provided by Java, a small note.
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(); }}// If there is no parent class, we return it. If the condition of the while loop is not satisfied, we exit.
return null;
}
Copy the code
Here’s a little tip. In the analysis process, combine the actual usage so that the analysis is clear. The general logic emerges
The next annotation I’ll focus on is the @import annotation. This is an important point in implementing the Springboot starter
How do you use @import on a daily basis? The first image must import a configuration class, which is fine. It could also import a normal Bean. After all, the configuration class is also decorated with the @Component annotation. 🐶
In addition, when importing beans, in addition to the above mentioned configuration class beans and ordinary beans, two different types in the general direction. There are also two special types of beans.
-
ImportSelector
An import selector, which makes decisions based on AnnotationMetadata of the configuration class, returns the class name (full class name) of the qualified bean to import, and provides Predicate to make a previous decision.
-
ImportBeanDefinitionRegistrar
BeanDefinition can be registered directly in BeanDefinitionRegistery. Just sign up and be done.
The two is the key of the Springboot automatic injection, ImportSelector, ImportBeanDefinitionRegistrar
Let’s move on to the method logic for handling @import annotations.
Note the “importCandidates” argument, which contains the @import annotation for the class to be imported, ConfigurationClassParser#getImports.
Encapsulates the information about the class to be imported into a SourceClass object. Let’s do the import here.
The specific code logic is as follows:
-
Look at the set of classes that need to be imported.
-
A stack is used to detect circular import problems, such as A importing B. B imports A.
-
Handles two special types
-
ImportSelector
Instantiation, his constructor can have the Environment, the BeanFactory, this, ResourceLoader can also realize several aware interface. EnvironmentAware, BeanFactoryAware, BeanClassLoaderAware, ResourceLoaderAware.
Invoke getExclusionFilter to form or relationship with the previous exclusionFilter. Call selectImports to return the full class name. ConfigClass, again calling processImports. For DeferredImportSelector, it is not called immediately, but in a collection (called after it has been parsed). Note that the ImportSelector implementation class is not put in the ConfigClass property, nor added to the BeanFactory.
-
ImportBeanDefinitionRegistrar
The instantiation process is the same as above. But there are added to ConfigClass importBeanDefinitionRegistrars inside him.
-
-
Continue to call the rest processConfigurationClass to parse, and their packaging as ConfigClass object, and will set the ConfigClass ImportBy properties for the current this ConfigClass object. Look at the method below. Candidate. AsConfigClass (configClass).
See the notes for specific logic.
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
// If no import is required, return directly
if (importCandidates.isEmpty()) {
return;
}
// This is A cyclic detection of the flag position to prevent A from importing B. B imports A and things like that happen.
// Put it on a stack and check it before processing. If yes, an error is reported
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
/ / into the stack
this.importStack.push(configClass);
try {
// Start the loop. Process the classes that need to be imported
for (SourceClass candidate : importCandidates) {
// Whether it is ImportSelector
if (candidate.isAssignable(ImportSelector.class)) {
// Get the Class object, which is the actual generated object of the ImportSelectorClass<? > candidateClass = candidate.loadClass();In the ImportSelector implementation class, the constructor can have environment, resourceLoader, and Registry
// At the same time, click on this method. It also calls the Aware method.
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
// Get the Predicate for the Bean you don't need
Predicate<String> selectorFilter = selector.getExclusionFilter();
if(selectorFilter ! =null) {
// And the previous composition or relationship
exclusionFilter = exclusionFilter.or(selectorFilter);
}
/ / if DeferredImportSelector, will add the ImportSelector to deferredImportSelectorHandler. This is really just a collection of deferredimPort Selectors.
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
Otherwise, it is a direct call, which returns the full class name of the bean.
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
// The Metadata will be loaded and retrieved. Encapsulate as a SourceClass object.
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
// continue through the import logic, that is, the ImportSelector, can return an ImportSelector at all, and does not deal with circular imports. Or can also be a ImportBeanDefinitionRegistrar.
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false); }}// One thing to note here: (here besides DeferredImportSelector can be added into deferredImportSelectorHandler, else, and it is not a ConfigClass properties, other can not added in the inside, yao got finished call directly. Not in the Spring container.
/ / if ImportBeanDefinitionRegistrar
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Same as above, instantiate, call aware methodClass<? > candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,this.environment, this.resourceLoader, this.registry);
/ / instantiate here, there is no direct call, but on the inside of the ConfigClass attributes (importBeanDefinitionRegistrars) inside
/ / this is a LinkHashMap, the key is ImportBeanDefinitionRegistrar, v is the current object Metadata parsing class
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
/ / if not the above two kinds of special type, may be a configuration class completely because of import, so again processConfigurationClass method.
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
The // asConfigClass method creates a new ConfigClass and sets the ImportBy property to the current ConfigClassprocessConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter); }}}catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop(); }}}Copy the code
Now, we’re done parsing the pare method, and the main purpose of this method is to wrap it as a ConfigClass object, and set the properties in it, and notice if the ConfigClass has any properties about ImportSelector in it. It will not be placed in ConfigClass, so our idea is to jump out of the parse method and move on.
But there’s a problem with not looking at the DeferredImportSelector call
This is actually called at the end of the parse method, which I’ve omitted here. 🐶
Continue down to parser.validate(); . Validation procedure is very simple, has written in processConfigBeanDefinitions code comments.
Started to walk to ConfigurationClassBeanDefinitionReader part below.
ConfigurationClassBeanDefinitionReader
After wrapping the configuration classes as ConfigClass objects in the previous step, it’s time to load them and make them BeanDefintion.
The first step is to create ConfigurationClassBeanDefinitionReader from loadBeanDefinitions# loadBeanDefinitions in, has been, You should see the following code (essentially passing in a collection of ConfigClass objects created in the previous step, iterating through the method below)
private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// the first part of the ConfigurationPhase is the REGISTER_BEAN.
// In addition, the @condition will be handled in conjunction with @import. As long as one of the @conditions of his ImportBy fails, he will not import.
if (trackedConditionEvaluator.shouldSkip(configClass)) {
//
String beanName = configClass.getBeanName();
// Remove the name from the file
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
//
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
// If the current ConfigClass is imported
if (configClass.isImported()) {
// Register with the SPring container
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
// Load the @bean annotation method, the process of parsing the Bean method is more, after a @bean method creation Bean analysis article in the introduction
Now, remember that this is also resolved as BeanDefinition registered in the container, but there are a few important points here, as discussed below
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
/ / processing ImportResource annotations, the key is the name of the configuration file, the value is the corresponding processing class, if it is. Groovy end, using GroovyBeanDefinitionReader, otherwise it is XmlBeanDefinitionReader
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
/ / processing said before ImportBeanDefinitionRegistrar, in fact, the loop calls.
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
Copy the code
@bean method parsing procedure
-
Because at sign Condition can also be used in methods, so I’m going to evaluate at sign Condition again. But the ConfigurationPhase is REGISTER_BEAN.
-
The nice thing about Spring is that it encapsulates it very well. A metadata can be a method or a class, so a method also has its own Metadata object (MethodMetadata), which gets @Bean properties and processes them. There’s an interesting point here
-
About the bean name
The name property of the @bean is an array and the name is an array. So, Spring does what it does: it takes the first element of the array, if any, as the bean’s name, the rest as aliases, and the method name is the bean’s name if not specified.
-
-
It also checks to see if the current bean has the same name as a previous bean that already exists in the bean factory as well as the name of the @bean configuration class.
-
It does not matter what modifiers are used for methods, such as private, public, etc., with a slight change to static, everything else is ok, and the current method will be used as the factory method to create the Bean, and the Scope annotation proxyModel will be parsed.
-
All that’s left is to parse the properties inside the @bean. And finally sign up.
This is just an overview of the @bean method, and I’ll look at the process and the implementation of the @Autowire injection.
Following the main line, after the BeanDefinition is registered, there’s a number before and a number after, find the configuration class that was registered in the Register, and parse it again.
Because in this process, it is likely that the injected bean will have a Configuration class in it. For example, XML can configure a Configuration class with the @Configuration annotation on it, or the beans injected by the @bean method can have a Configuration class. Moreover ImportBeanDefinitionRegistrar also can import.
At this point, parsing and registering as A BeanDefinition is complete.
There is a point that is used in the following content, which is analyzed here. This point appears in the above code, but I did not go into detail
ConfigurationClassUtils#checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory)
First ConfigurationClassUtils is a utility class for the Configuration class
This method determines whether the current Bean is a configuration class and sets the Bean properties.
I want to point out that the @configuration attribute for proxyBeanMethods, which defaults to true, sets the BeanDefinition attribute CONFIGURATION_CLASS_ATTRIBUTE, which is useful in the following sections.
Its function is not described here, but will be described in the following sections.
public static boolean checkConfigurationClassCandidate( BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
String className = beanDef.getBeanClassName();
if (className == null|| beanDef.getFactoryMethodName() ! =null) {
return false;
}
AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
// Can reuse the pre-parsed metadata from the given BeanDefinition...
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
// Check already loaded Class if present...
// since we possibly can't even load the class file for this Class.
// The following classes cannot be used as configuration classesClass<? > beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
BeanPostProcessor.class.isAssignableFrom(beanClass) ||
AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
EventListenerFactory.class.isAssignableFrom(beanClass)) {
return false;
}
metadata = AnnotationMetadata.introspect(beanClass);
}
else {
try {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: " +
className, ex);
}
return false; }}// This is where the Configuration annotation is obtained from the bean's metadata
// Set the bean attribute CONFIGURATION_CLASS_ATTRIBUTE using proxyBeanMethods in the annotation.
// Proxy defaults to true: CONFIGURATION_CLASS_FULL, otherwise CONFIGURATION_CLASS_LITE
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if(config ! =null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
else if(config ! =null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
// Get the ORDER annotation and set the properties
Integer order = getOrder(metadata);
if(order ! =null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
}
return true;
}
Copy the code
postProcessBeanFactory
Main functions:
Prepare for runtime service bean requests by replacing configuration classes with CGLIB enhanced subclasses.
First of all, as a BeanFactoryPostprocess, it must depend on how it is done in this method.
So what I’m doing here is going through all the Beandefinitions and finding out that there’s a CONFIGURATION_CLASS_ATTRIBUTE, and it’s CONFIGURATION_CLASS_FULL, in a set, Here ConfigurationClassEnhancer the main method is used to create a proxy class, and will be the configuration inside a class BeanDefinition BeanClass is set to the proxy class. If @Configuration proxyMethods is false, the proxy will not be created.
The problem?
Why do you want to be an agent?
In fact, the purpose of this essence is to solve the problem of generating multiple beans with nested methods. For example, now there are two methods A and B, both annotated with the @bean annotation, if A method calls B. Does it add B to the container when it creates A, definitely not, does it create B when it calls B? Will be. And it’s going to add to the container, so the problem is, B creates it twice. So, the goal here is to solve the problem of creating beans when this method is nested.
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {... omittedif (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
if(! (beanDefinstanceof 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;
}
/ / start the agent, the point is that ConfigurationClassEnhancer class how to do,
// Autoproxyutils.preserve_target_class_attribute, boils.true is also set
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 classClass<? > configClass = beanDef.getBeanClass(); 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); }}}Copy the code
ConfigurationClassEnhancer
The proxy object of the production proxy Configuration object is obtained by subclassing the proxy object using cglib. The @bean method is overridden. When the @bean method is actually called to create an object, the object will be created by the SPring container. Otherwise, it will be handled by the proxy object.
First of all, this method itself is very simple, think about it when using Cglib: set superClass, set callback, set filter. And then a create. In the same way that proxy objects are made easier by Cglib and Proxy (Java), the focus is on what happens before and after the target method.
So let’s take a closer look at this class.
Since it is Cglib, it depends on what happens in the callbacks it adds, and if there are multiple callbacks, it depends on its CallbackFilter.
Callback
There are three callback operations, the main two, the specific code analysis will not be done, because there are too many, the general function of the method and some attention to the place, the specific method in the
Org. Springframework. Context. The annotation. ConfigurationClassEnhancer class, find the graphic attributes. Click to see the implementation of the corresponding class.
The problem
Since you want to retrieve beans from the BeanFactory. When was the BeanFactory injected? What is the name of the corresponding field?
First of all, there is EnhancedConfiguration in ConfigurationClassEnhancer interface, he is an inherited BeanFactoryAware, is an empty interface. He has the proxy class implement this interface when creating the proxy object. Specific code in ConfigurationClassEnhancer# newEnhancer method.
So if you want to use it, you need to know what it’s called, so that you can get the beanFactory field. In BeanMethodInterceptor, the first line of getBeanFactory in Intercept contains the name of the field, which is called beanFactory from the proxy class. In BeanMethodInterceptor, the first line of getBeanFactory in Intercept contains the name of the field, which is called beanFactory from the proxy class. In BeanMethodInterceptor getBeanFactory intercept in the first line of code is the name of the field, will be obtained from the proxy class field is the beanFactory name field.
When did you set it up? Remember, cglib’s proxy generates bytecode and loads it in memory to generate proxy objects. That must have been written in when the bytecode was being generated. Concrete is in ConfigurationClassEnhancer# BeanFactoryAwareGeneratorStrategy class. This is a hook function provided by cglib. The transform method will generate a field named $$beanFactory of type beanFactory.
In ConfigurationClassPostProcessor postProcessBeanFactory added ImportAwareBeanPostProcessor (InstantiationAwareBeanPostProce SsorAdapter implementation class). This overrides the two postProcessProperties and method judgments to determine whether the current bean is an EnhancedConfiguration, and then sets the BeanFactory.
At the time of setting will call to BeanFactoryAwareMethodInterceptor intercept method. It’s going to set the value by reflection.
BeanMethodInterceptor
Intercepting the @Configuration class, which annotates the @bean method, ensures that the Bean operation is handled correctly. Proxies such as Scope and Aop.
That is, before calling the @bean method, this will work. It will check if the desired Bean is in the BeanFactory. If so, it will return it directly from the BeanFactory, otherwise it will follow the standard creation process. For example, if you have two methods, A and B, both annotated with the @bean annotation, if you call B from A. Does it add B to the container when it creates A, definitely not, does it create B when it calls B? Will be. And it’s going to add to the container, so the problem is, B creates it twice. So, the goal here is to solve the problem of creating beans when this method is nested. There’s a problem. Why cglib instead of Java Proxy?
I think the reasons are as follows:
- Cglib is more powerful than Proxy. One is a class and one is an interface.
- You can’t write configuration classes that require the user to enforce an interface. And the methods in the interface have to be specified, which is obviously not possible.
- If enforcing an interface is allowed, Java proxies cannot discover calls between methods in a class. But Cglib does. More on that later.
All right, let’s keep looking.
BeanFactoryAwareMethodInterceptor
Intercepts any BeanFactoryAware#setBeanFactory(BeanFactory) method in the @configuration class in order to record the BeanFactory.
NoOp.INSTANCE
Do nothing. Do nothing
CallbackFilter
ConditionalCallback = ConditionalCallback = ConditionalCallback = ConditionalCallback = ConditionalCallback = ConditionalCallback = ConditionalCallback = ConditionalCallback
Here has analysis the ConfigurationClassPostProcessor parsing operation for @ the Configuration notes.
The summary is as follows:
The body is divided into two parts: parsing and loading. Create a proxy object for the configuration class.
Parse the Configuration class into a ConfiClass object, encapsulate all the annotations available in the @Configuration class, and set the @condition annotation judgment, @import annotation processing, @bean method, etc., in this class. After parsing, start loading. The loading is done by creating the corresponding BeanDefinition from the ConfigClass that was created earlier and registering it with the BeanFactory. After the whole work is the BeanFactory create good, before the normal BeanFactoryPostProcess postProcessBeanDefinitionRegistry methods do.
The BeanFactoryPostProcess method creates a proxy object for the Configuration class through the @Configuration annotated ProxyMethod. The default is to create. Cglib is a subclass of Cglib to generate configuration classes. It does proxying. The purpose of proxying is to prevent the problem of Bean object inconsistency caused by nested method calls. BeanFactoryPostProcess and BeanFactoryAwareMethodInterceptor, when the call to see if the current bean is created first, if you create, call the superclass (normal operation, creating an object, that is) if not, Get the Bean directly from the BeanFactory. This ensures that the bean generated when the method is called in a nested manner is unique in Spring.
Method nested calls:
Let’s say we have a configuration class:
A method with two beans, TestBean requires a TestBean1. I call the testBean1 method inside the testBean. That’s how you solve the problem. Wouldn’t it be a problem if methods nested calls produced different objects?
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class config {
@Bean
public TestBean1 testBean1(a){
TestBean1 testBean1 = new TestBean1();
testBean1.setName("testbean1");
return testBean1;
}
@Bean
public TestBean testBean(a){
TestBean testBean = new TestBean();
testBean.setAge(12);
testBean.setName("testBean");
TestBean1 testBean1 = testBean1();
testBean.setTestBean1(testBean1);
returntestBean; }}Copy the code
test
try (
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(config.class);
) {
TestBean bean = context.getBean(TestBean.class);
System.out.println(bean);
TestBean1 bean1 = context.getBean(TestBean1.class);
System.out.println(bean1);
} catch (Throwable e) {
e.printStackTrace();
}
Copy the code
The results of
As you can see, the TestBean1 object is the same in both places.
What if he were told not to generate the configuration classes for the proxy, and what would be the result of the above problem? Try @Configuration(proxyBeanMethods = false)
Looking at the results
As you can see, TestBean1 in the nested method is not managed by Spring.
As for the blog, I take it as my notes, and there are a lot of contents in it that reflect my thinking process. As my thinking is limited, there are inevitably some discrepancies. If there are any questions, please point them out. Discuss together. thank you