catalogue
1. postProcessBeanDefinitionRegistry()
(1) @ Conditional annotation parsing
(2) Parsing of @ Component, @ ComponentScan, @ Import, @ ImportSource and other annotations
a. @Component, @ ComponentScan, @ Import, @ ImportSource resolution
Now, when using the Spring framework, we basically inject beans based on annotations, such as @ PropertySource, @ ComponentScan, @ Bean, @ Import, @ ImportSource, @ Configuration and ImportSelector. Their resolution is inseparable from the ConfigurationClassPostProcessor class, These annotations are to scan (or Import) and put the scanned (or imported) classes into the Spring container to become BeanDefinition.
Previously, we registered Spring context based on XML. Now, annotation context has become popular, as follows:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanBean.class);
Import package path through @ ComponentScan annotation:
@Component @ComponentScan(value = "com.baec.kieasar") public class ScanBean { }
Or:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.baec.kieasar");
Let's look at the constructor of AnnotationConfigApplicationContext:
public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) { super(beanFactory); this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); }
Enter the constructor of AnnotatedBeanDefinitionReader:
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) { this(registry, getOrCreateEnvironment(registry)); }
Enter this:
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); Assert.notNull(environment, "Environment must not be null"); this.registry = registry; this.conditionEvaluator = new ConditionEvaluator(registry, environment, null); AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); }
Enter annotationconfigutils Registerannotationconfigprocessors() method
Category: org springframework. context. annotation. AnnotationConfigUtils
public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) { registerAnnotationConfigProcessors(registry, null); }
ConfigurationClassPostProcessor is registered here:
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, @Nullable Object source) { DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry); if (beanFactory != null) { // Set the OrderComparator of beanFactory to annotationawarerder comparator // It is a comparator for sorting, such as new ArrayList < > () sort(Comparator) if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) { beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); } // The contextannotationautowirecandideresolver class is an annotation candidate parser (mainly dealing with @ Lazy), which is used to infer whether a Bean needs dependency injection if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) { beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); } } Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8); // Register BeanDefinition of ConfigurationClassPostProcessor type if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); } // Register the BeanDefinition of the AutowiredAnnotationBeanPostProcessor type if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); } // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor. // Register BeanDefinition of CommonAnnotationBeanPostProcessor type if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor. // Register BeanDefinition of PersistenceAnnotationBeanPostProcessor type if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(); try { def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, AnnotationConfigUtils.class.getClassLoader())); } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex); } def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)); } // Register the BeanDefinition of EventListener methodprocessor type to process the @ EventListener annotation if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME)); } // Register the BeanDefinition of defaulteventlistener factory type to handle the @ EventListener annotation if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME)); } return beanDefs; }
First, let's take a look at the inheritance relationship of the ConfigurationClassPostProcessor class:
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware {
This class implements the BeanDefinitionRegistryPostProcessor interface to operate (register) BeanDefinition and add it to BeanDefinitionRegistry. The PriorityOrdered interface is used for sorting. It inherits the Ordered interface and overrides the getOrder() method:
@Override // Set the execution sequence. The smaller the value, execute first public int getOrder() { return Ordered.LOWEST_PRECEDENCE; // within PriorityOrdered }
1. postProcessBeanDefinitionRegistry()
At the same time, the BeanDefinitionRegistyPostProcessor interface is implemented. It is bound to implement the postProcessBeanDefinitionRegistry() method, register BeanDefinition and scan configuration classes. Let's look at this method first:
/** * Derive further bean definitions from the configuration classes in the registry. * This is the method of beanfactoryprocessor of BeanDefinitionRegistryPostProcessor interface, which has been rewritten */ @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { int registryId = System.identityHashCode(registry); if (this.registriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry); } if (this.factoriesPostProcessed.contains(registryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + registry); } this.registriesPostProcessed.add(registryId); // TODO core logic parses configuration classes, focusing on ---- > processConfigBeanDefinitions(registry); }
Enter the processConfigBeanDefinitions() method of this class:
/** * Build and validate a configuration model based on the registry of * {@link Configuration} classes. * In this method, all beanDefinition operations (parsing) are performed */ public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); // Get all beanNames String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); // If there is this identifier, it will not be processed. "configurationClass" indicates the annotations, such as @ 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); } } // Judge whether it is a configuration class and whether there is a BeanDefinition to be processed. If so, put it into configCandidates else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found // If the container is empty, that is, the @ Configuration class cannot be found, return immediately if (configCandidates.isEmpty()) { return; } // Sort by previously determined @Order value, if applicable // Sort the BeanDeanDefinition to be processed. The smaller the value, the higher the value 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; } } } if (this.environment == null) { this.environment = new StandardEnvironment(); } // Parsing configuration class, BeanDefinition parser -- ConfigurationClassParser // Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); // Resolved Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); do { StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse"); // The core process of TODO analysis, importance: 5 // Collect first, such as @ Import, @ ComponentScan, @ Bean and ImportSelector, and finally package them into BeanDefinition // After parsing, the BeanDefinitionHolder will be encapsulated into ConfigurationClass // In this process, steps such as scanning and importing will be performed to find other configurationclasses parser.parse(candidates); parser.validate(); // ConfigurationClass is equivalent to all configuration classes (including itself) parsed up to now Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } // TODO calls again. The parse() method above has not scanned some annotations, so it is called here. // After parsing, the BeanDefinitionHolder will be encapsulated into ConfigurationClass // In this process, steps such as scanning and importing will be performed to find other configurationclasses this.reader.loadBeanDefinitions(configClasses); // The class that has been parsed is recursive again, and there is no need to repeat the processing alreadyParsed.addAll(configClasses); processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end(); candidates.clear(); // Compare the differences. If a new BeanDefiniton is found, go through the parsing process again 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); // Check whether the extra BeanDefinition is a configuration class and needs to be resolved 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(); } }
Enter parse() method:
Category: org springframework. context. annotation. ConfigurationClassParser
// Analytical process public void parse(Set<BeanDefinitionHolder> configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { // BeanDefinition obtained by scanning annotations if (bd instanceof AnnotatedBeanDefinition) { // Look at this method***** parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } // BeanDefinition not obtained by scanning annotations 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); } } // TODO handles the resolution of the DeferredImportSelector interface. All current configuration classes are not executed until they are resolved // DeferredImportSelector means deferred ImportSelector. Normal ImportSelector is executed during the process of parsing configuration class this.deferredImportSelectorHandler.process(); }
Enter the parse() method of this class:
Category: org springframework. context. annotation. ConfigurationClassParser
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException { // Encapsulate the metadata object and beanName into a ConfigurationClass object processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER); }
Enter the processConfigurationClass() method of this class. Here is the real parsing logic:
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException { // Support for @ Conditional annotation to filter out classes that do not need to be instantiated if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } ConfigurationClass existingClass = this.configurationClasses.get(configClass); if (existingClass != null) { if (configClass.isImported()) { // If a class is imported by two classes, it meets this condition and is added to the importBy attribute 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. // This object can be understood as corresponding to the class or interface, and then wrap the metadata object in it SourceClass sourceClass = asSourceClass(configClass, filter); do { // TODO core code -- > sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter); } while (sourceClass != null); // After collection, put it into the Map this.configurationClasses.put(configClass, configClass); }
(1) @ Conditional annotation parsing
First, the @ Conditional annotation is parsed. The @ Conditional annotation is widely used in Springboot to set the instantiation conditions of this class, such as:
@Conditional(value = {CustomCondition.class}) @Primary @Bean public Lison lison() { return new Lison(); }
The instantiation of Lison class needs to rely on CustomCondition or array. The dependent class CustomCondition needs to implement the Condition interface and the matches() method:
public class CustomCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return true; } }
Enter the @ Conditional annotation parsing method shouldSkip():
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) { return false; } if (phase == null) { if (metadata instanceof AnnotationMetadata && ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); } return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); } List<Condition> conditions = new ArrayList<>(); // Gets the value of the @ Conditional annotation for (String[] conditionClasses : getConditionClasses(metadata)) { for (String conditionClass : conditionClasses) { // Reflection instance Condition object Condition condition = getCondition(conditionClass, this.context.getClassLoader()); conditions.add(condition); } } // Sorting (support Order interface and @ Order annotation) AnnotationAwareOrderComparator.sort(conditions); for (Condition condition : conditions) { ConfigurationPhase requiredPhase = null; if (condition instanceof ConfigurationCondition) { requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); } // Loop calls the matches() method of each condition if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { return true; } } return false; }
(2) Parsing of @ Component, @ ComponentScan, @ Import, @ ImportSource and other annotations
Go back to the processConfigurationClass() method and enter the core code doProcessConfigurationClass() method:
Category: org springframework. context. annotation. ConfigurationClassParser
/* * TODO The annotation parsing core method SourceClass represents the encapsulation of the class. It is responsible for different directions. There is a metaData object in it */ @Nullable protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException { // Judge whether there is @ Component annotation on the class if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // Recursively process any member (nested) classes first // TODO recursively handles internal class processing with @ Component annotation processMemberClasses(configClass, sourceClass, filter); } // Process any @PropertySource annotations // Handling @ PropertySources and @ PropertySource annotations for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { // TODO core logic - > processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } // Process any @ComponentScan annotations // Process @ ComponentScans and @ ComponentScan annotations from sourceclass GetMetadata () gets the annotation and puts it into the Set container Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); // Need to skip 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 // The logic in the parse() method under TODO is similar to the logic of < component scan > custom label resolution, // Scan through the ClassPathBeanDefinitionScanner to get the beandefinition and register it with the Spring container 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 // Here, after recursively scanning @ Component to generate beanDefinition, it returns to check whether there are special annotations on the class for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } // Judge whether it is a candidate BeanDefinition. If so, parse() is a recursive process if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } // Process any @Import annotations // TODO handles the @ Import annotation. The getImports(sourceClass) method is used to collect the @ Import annotation on the class and encapsulate it into a SourceClass processImports(configClass, sourceClass, getImports(sourceClass), filter, true); // Process any @ImportResource annotations // The @ ImportResource annotation has little effect, and the xml file is loaded 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); // Establish the mapping relationship between xml file and reader configClass.addImportedResource(resolvedResource, readerClass); } } // Process individual @Bean methods // (emphasis) process @ Bean annotations, collect methods with @ Bean annotations, and sourceClass is the current class Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { // Add to ConfigurationClass configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces // Handle the interface corresponding to the @ Bean annotation method, and the logic is similar processInterfaces(configClass, sourceClass); // Process superclass, 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 return sourceClass.getSuperClass(); } } // No superclass -> processing is complete return null; }
In the above method, we use a graph to summarize:
a. @Component, @ ComponentScan, @ Import, @ ImportSource resolution
For the judgment of @ Component, @ ComponentScan, @ Import, @ ImportSource annotations, in the isConfigurationCandidate() method:
Category: org springframework. context. annotation. ConfigurationClassUtils
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) { // Do not consider an interface or an annotation... if (metadata.isInterface()) { return false; } // Any of the typical annotations found? // If one of the @ Component, @ ComponentScan, @ Import, @ ImportSource annotations exists, it is the lite configuration class for (String indicator : candidateIndicators) { if (metadata.isAnnotated(indicator)) { return true; } } // Finally, let's look for @Bean methods... // If there is a @ Bean annotated method, it is the lite configuration class try { return metadata.hasAnnotatedMethods(Bean.class.getName()); } catch (Throwable ex) { if (logger.isDebugEnabled()) { logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex); } return false; } }
abstract class ConfigurationClassUtils { public static final String CONFIGURATION_CLASS_FULL = "full"; public static final String CONFIGURATION_CLASS_LITE = "lite"; public static final String CONFIGURATION_CLASS_ATTRIBUTE = Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass"); private static final String ORDER_ATTRIBUTE = Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "order"); private static final Log logger = LogFactory.getLog(ConfigurationClassUtils.class); private static final Set<String> candidateIndicators = new HashSet<>(8); static { candidateIndicators.add(Component.class.getName()); candidateIndicators.add(ComponentScan.class.getName()); candidateIndicators.add(Import.class.getName()); candidateIndicators.add(ImportResource.class.getName()); }
b. @Configuration resolution
Spring judges the @ Configuration annotated classes based on the following:
- If there is a class with @ Configuration annotation and proxyBeanMethods is true or null (default), it is a Full Configuration class;
- If there is @ Configuration annotation, but proxyBeanMethods is false, configure the class for lite;
- If the @ Configuration annotation does not exist, but @ Component, @ ComponentScan, @ Import, @ ImportSource exist, it is the lite Configuration class;
- If there is no @ Configuration annotation, but the method with @ Bean annotation is the lite Configuration class.
c. @ComponentScans parsing
A ClassPathBeanDefinitionScanner scanner will be created and the doScan() method will be called:
Category: org springframework. context. annotation. ClassPathBeanDefinitionScanner
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { // Obtain the BeanDefinition according to the scanning path Set<BeanDefinition> candidates = findCandidateComponents(basePackage); // Parse class for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { // Resolve @ lazy, @ Primary, @ DependsOn, @ Role, @ Description AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } // Check whether the beanName already exists in the Spring container if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); // register registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
In the doProcessConfigurationClass() method, only the @ ComponentScan annotation is parsed, and the BeanDefinition will be generated by scanning. Other annotations are added to the configuration attribute of the ConfigurationClass class (not parsed at this time).
d. Internal class processing
Look at the processMemberClasses(configClass, sourceClass, filter) of this class, and handle those annotated with @ Component. This is the processing of internal classes:
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException { // Get the inner class of this class and wrap it into a sourceClass object. You can go in and have a look at the getMemberClasses() method Collection<SourceClass> memberClasses = sourceClass.getMemberClasses(); if (!memberClasses.isEmpty()) { List<SourceClass> candidates = new ArrayList<>(memberClasses.size()); for (SourceClass memberClass : memberClasses) { // If it needs to be processed, there are @ Bean, @ Component and other annotations if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) && !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) { candidates.add(memberClass); } } // sort OrderComparator.sort(candidates); // Loop through each inner class for (SourceClass candidate : candidates) { if (this.importStack.contains(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { // Candidate is the child class, configClass is the parent class, and candidate is the internal class of configClass. It is processed recursively processConfigurationClass(candidate.asConfigClass(configClass), filter); } finally { this.importStack.pop(); } } } } }
ConfigurationClass is used to encapsulate the configuration class. Let's take a look at its member properties:
// This class is the wrapper of a class and the function of collecting annotations (including internal classes) final class ConfigurationClass { // Basic source information in class, class, method and annotation information private final AnnotationMetadata metadata; // Encapsulation of stream in class private final Resource resource; @Nullable private String beanName; // Represents which class is imported, which is loaded with referenced or external classes private final Set<ConfigurationClass> importedBy = new LinkedHashSet<>(1); // If the class has @ Bean annotated methods, put them in this container private final Set<BeanMethod> beanMethods = new LinkedHashSet<>(); // If the class has @ ImportedResources annotation, put it into this container private final Map<String, Class<? extends BeanDefinitionReader>> importedResources = new LinkedHashMap<>(); // If the class implements the ImportBeanDefinitionRegistrar interface, put it in this container private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>();
d. @PropertySource parsing
Enter the processPropertySource(propertySource) method of this class, parse the configuration file and put it in the environment:
private void processPropertySource(AnnotationAttributes propertySource) throws IOException { String name = propertySource.getString("name"); if (!StringUtils.hasLength(name)) { name = null; } String encoding = propertySource.getString("encoding"); if (!StringUtils.hasLength(encoding)) { encoding = null; } // Get profile path String[] locations = propertySource.getStringArray("value"); Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required"); boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound"); Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory"); PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ? DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass)); for (String location : locations) { try { // Replace placeholder String resolvedLocation = this.environment.resolveRequiredPlaceholders(location); // Load the configuration file as a stream and encapsulate it into a Resource object Resource resource = this.resourceLoader.getResource(resolvedLocation); // Load the configuration value property in the Resource, encapsulate it into a Properties object, and create a PropertySource object to add to the Environment addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding))); } catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) { // Placeholders not resolvable or resource not found when trying to open it if (ignoreResourceNotFound) { if (logger.isInfoEnabled()) { logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage()); } } else { throw ex; } } } }
Composition of SourceClass:
private class SourceClass implements Ordered { private final Object source; // Class or MetadataReader private final AnnotationMetadata metadata;
e. @Import parse
The @ Imprt annotation is used to import a class into the Spring container and instantiate it. For example:
@Component // Although Import instantiates a class, the imported class can implement some interfaces, such as ImportSelector and ImportBeanDefinitionRegistar interfaces, // Then we can call the methods in the interface, but if the class injected through the @ Component annotation implements these interfaces, it cannot call its methods. @Import(DeferredImportSelectorDemo.class) // @Import({DeferredImportSelectorDemo.class, LisonSelectImport.class, AwareBean.class}) @EnableAspectJAutoProxy public class ImportBean { }
The ImportBean in the example is annotated with @ Import. The parsing process is to wrap the ImportBean class into a SourceClass object, then get the @ Import annotation from the metadata method, get its value value, and put it into the Set container. The value value can also be an array and Import multiple classes.
public class LisonSelectImport implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { MergedAnnotations annotations = importingClassMetadata.getAnnotations(); MergedAnnotation<EnableAspectJAutoProxy> eas = annotations.get(EnableAspectJAutoProxy.class); Object proxyTargetClass = eas.getValue("proxyTargetClass").get(); // The fully qualified name of the class, return new String[]{Jack.class.getName()}; } }
At the same time, you can also get the annotation information on the imported class ImportBean, such as the annotation @ EnableAspectJAutoProxy to enable the aspect function. This is how Springboot automatic configuration is implemented.
Enter the core method processImports() for @ Import annotation parsing:
Category: this category org springframework. context. annotation. ConfigurationClassParser
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter, boolean checkForCircularImports) { // If there is no @ Import annotation, it will be returned directly and will not be processed if (importCandidates.isEmpty()) { return; } if (checkForCircularImports && isChainedImportOnStack(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { // Every @ Import above the loop class for (SourceClass candidate : importCandidates) { // If an ImportSelector type is imported if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class<?> candidateClass = candidate.loadClass(); // Reflection instantiation ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry); Predicate<String> selectorFilter = selector.getExclusionFilter(); if (selectorFilter != null) { exclusionFilter = exclusionFilter.or(selectorFilter); } // If it is DeferredImportSelector type if (selector instanceof DeferredImportSelector) { // It's complicated. It's automatically configured in Springboot this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else { // Call the ImportSelectors() method to return all the beannames that need to be imported into the Spring container. Here, you will call the implementation class method you wrote, and return the full qualified name of the class. // The selectImports() method is called before collection. String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter); // Recursive processing. Sometimes the class imported has @ Import annotation processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false); } } // If the class of @ Import is of type ImportBeanDefinitionRegistrar else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions Class<?> candidateClass = candidate.loadClass(); // Reflection instantiation ImportBeanDefinitionRegistrar registrar = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry); // Added to the ImportBeanDefinitionRegistrar container, registerBeanDefinitions() is not invoked here, and the method of calling the interface after collection is implemented. // addImportBeanDefinitionRegistrar() maintains the mapping relationship between an instance that implements the ImportBeanDefinitionRegistrar interface and the annotation metadata of this class configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class // If it is not an ImportSelector or ImportBeanDefinitionRegistrar type, treat it as a @ Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); // If the imported class has other annotations, enter here and resolve it by recursive call processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter); } } }
Next, go back to the processConfigBeanDefinitions() method and look at this reader. Loadbeandefinitions () method, which is a method for processing logic and parsing annotations for @ Bean, @ ImportedResource and ImportBeanDefinitionRegistrar. After the configuration class is resolved, it will be added to the attribute corresponding to the current ConfigurationClass, and then it will be further resolved (or built) into BeanDefiniton.
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) { TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator(); for (ConfigurationClass configClass : configurationModel) { // Annotation collection, encapsulating the class itself and its @ Bean annotated methods into BeanDefinition loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } }
This method mainly parses the configuration class to generate BeanDefinition.
private void loadBeanDefinitionsForConfigurationClass( ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { // Do you want to skip 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; } // @The imported class and the internal class become BeanDefinition here if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } // @The method of Bean annotation becomes BeanDefinition for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } // Process @ ImportResource annotation and xml parsing process loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); // Handle the ImportBeanDefinitionRegistrar interface and call the registrerBeanDefinitions() method loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }
Enter loadBeanDefinitionsForBeanMethod():
Category: org springframework. context. annotation. ConfigurationClassBeanDefinitionReader
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { ConfigurationClass configClass = beanMethod.getConfigurationClass(); MethodMetadata metadata = beanMethod.getMetadata(); 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; } AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class); Assert.state(bean != null, "No @Bean annotation attributes"); // Consider name and any aliases 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) { this.registry.registerAlias(beanName, alias); } // Has this effectively been overridden before (e.g. via XML)? // If there is a duplicate @ Bean annotation method name (such as method overload), return and no new one will be generated 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; } // If it does not exist, a new BeanDefinition is generated ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata); beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource())); 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 beanDef.setFactoryBeanName(configClass.getBeanName()); beanDef.setUniqueFactoryMethodName(methodName); } if (metadata instanceof StandardMethodMetadata) { beanDef.setResolvedFactoryMethod(((StandardMethodMetadata) metadata).getIntrospectedMethod()); } beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR); beanDef.setAttribute(org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor. SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE); AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata); Autowire autowire = bean.getEnum("autowire"); if (autowire.isAutowire()) { beanDef.setAutowireMode(autowire.value()); } boolean autowireCandidate = bean.getBoolean("autowireCandidate"); if (!autowireCandidate) { beanDef.setAutowireCandidate(false); } String initMethodName = bean.getString("initMethod"); if (StringUtils.hasText(initMethodName)) { beanDef.setInitMethodName(initMethodName); } String destroyMethodName = bean.getString("destroyMethod"); beanDef.setDestroyMethodName(destroyMethodName); // Consider scoping 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 (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); } if (logger.isTraceEnabled()) { logger.trace(String.format("Registering bean definition for @Bean method %s.%s()", configClass.getMetadata().getClassName(), beanName)); } // Register the parsed BeanDefiniton, and check whether overwriting is allowed here this.registry.registerBeanDefinition(beanName, beanDefToRegister); } protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String beanName) { if (!this.registry.containsBeanDefinition(beanName)) { return false; } BeanDefinition existingBeanDef = this.registry.getBeanDefinition(beanName); // Is the existing bean definition one that was created from a configuration class? // -> allow the current bean method to override, since both are at second-pass level. // However, if the bean method is an overloaded case on the same configuration class, // preserve the existing bean definition. // If BeanDefinition already exists in the beanName corresponding to @ Bean, set isFactoryMethodUnique of this BeanDefinition to false // Later, when you create a Bean based on this BeanDefinition, you will know that there is more than one Bean if (existingBeanDef instanceof ConfigurationClassBeanDefinition) { ConfigurationClassBeanDefinition ccbd = (ConfigurationClassBeanDefinition) existingBeanDef; if (ccbd.getMetadata().getClassName().equals( beanMethod.getConfigurationClass().getMetadata().getClassName())) { if (ccbd.getFactoryMethodMetadata().getMethodName().equals(ccbd.getFactoryMethodName())) { ccbd.setNonUniqueFactoryMethodName(ccbd.getFactoryMethodMetadata().getMethodName()); } return true; } else { return false; } } // A bean definition resulting from a component scan can be silently overridden // by an @Bean method, as of 4.2... if (existingBeanDef instanceof ScannedGenericBeanDefinition) { return false; } // Has the existing bean definition bean marked as a framework-generated bean? // -> allow the current bean method to override it, since it is application-level if (existingBeanDef.getRole() > BeanDefinition.ROLE_APPLICATION) { return false; } // At this point, it's a top-level override (probably XML), just having been parsed // before configuration class processing kicks in... if (this.registry instanceof DefaultListableBeanFactory && !((DefaultListableBeanFactory) this.registry).isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(), beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef); } if (logger.isDebugEnabled()) { logger.debug(String.format("Skipping bean definition for %s: a definition for bean '%s' " + "already exists. This top-level bean definition is considered as an override.", beanMethod, beanName)); } return true; }
Enter isOverriddenByExistingDefinition() of this class to determine whether to repeat scanning:
protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String beanName) { if (!this.registry.containsBeanDefinition(beanName)) { return false; } BeanDefinition existingBeanDef = this.registry.getBeanDefinition(beanName); // Is the existing bean definition one that was created from a configuration class? // -> allow the current bean method to override, since both are at second-pass level. // However, if the bean method is an overloaded case on the same configuration class, // preserve the existing bean definition. // ConfigurationClassBeanDefinition is a BeanDefiniton parsed by @ Bean annotation method, // If BeanDefinition already exists in the beanName corresponding to @ Bean, set isFactoryMethodUnique of this BeanDefinition to false // Later, when you create a Bean based on this BeanDefinition, you will know that there is more than one Bean if (existingBeanDef instanceof ConfigurationClassBeanDefinition) { ConfigurationClassBeanDefinition ccbd = (ConfigurationClassBeanDefinition) existingBeanDef; if (ccbd.getMetadata().getClassName().equals( beanMethod.getConfigurationClass().getMetadata().getClassName())) { if (ccbd.getFactoryMethodMetadata().getMethodName().equals(ccbd.getFactoryMethodName())) { ccbd.setNonUniqueFactoryMethodName(ccbd.getFactoryMethodMetadata().getMethodName()); } return true; } else { return false; } } // A bean definition resulting from a component scan can be silently overridden // by an @Bean method, as of 4.2... // ScannedGenericBeanDefinition is the BeanDefinition obtained after scanning if (existingBeanDef instanceof ScannedGenericBeanDefinition) { return false; } // Has the existing bean definition bean marked as a framework-generated bean? // -> allow the current bean method to override it, since it is application-level if (existingBeanDef.getRole() > BeanDefinition.ROLE_APPLICATION) { return false; } // At this point, it's a top-level override (probably XML), just having been parsed // before configuration class processing kicks in... if (this.registry instanceof DefaultListableBeanFactory && !((DefaultListableBeanFactory) this.registry).isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(), beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef); } if (logger.isDebugEnabled()) { logger.debug(String.format("Skipping bean definition for %s: a definition for bean '%s' " + "already exists. This top-level bean definition is considered as an override.", beanMethod, beanName)); } return true; }
- @Bean annotated beans will overwrite the @ Component annotation;
- If all are @ Bean annotated, overwrite;
- @The Component annotation is repeated and an error is reported.
The previous method scanning and enhancement have been completed, and many beandefinitions have been obtained. However, the instantiated object of the Bean has not been generated, and the method corresponding to the @ Bean annotation has not been called. It will be called only when the Bean is created and instantiated and inferred from the constructor.
2. postProcessBeanFactory()
Next, look at another method of the ConfigurationClassPostProcessor class, postProcessBeanFactory(), which is the method of the beanfactoryprocessor interface, which is equivalent to the post processor of BeanFactory. This method is rewritten here to generate a proxy for the class with @ Configuration annotation:
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); } // For Configuration classes annotated with @ Configuration, CGLIB proxy, bytecode enhancement enhanceConfigurationClasses(beanFactory); beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory)); }
Enter the enhanceConfigurationClasses() method:
Category: org springframework. context. annotation. ConfigurationClassPostProcessor
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) { Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>(); // Traverse and find the configuration class according to beanName 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 not, load the class if (!abd.hasBeanClass()) { try { abd.resolveBeanClass(this.beanClassLoader); } catch (Throwable ex) { throw new IllegalStateException( "Cannot load configuration class: " + beanDef.getBeanClassName(), ex); } } } // Judge whether the configClassAttr attribute obtained by parsing the configuration class is full. If so, add the configBeanDefs container 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; } // For the configuration class with full attribute, generate a proxy class and set it to BeanDefinition 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(); // Generate proxy object for configuration 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); } } }
Enter the enhance() method:
Category: org springframework. context. annotation. ConfigurationClassEnhancer
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) { if (EnhancedConfiguration.class.isAssignableFrom(configClass)) { if (logger.isDebugEnabled()) { logger.debug(String.format("Ignoring request to enhance %s as it has " + "already been enhanced. This usually indicates that more than one " + "ConfigurationClassPostProcessor has been registered (e.g. via " + "<context:annotation-config>). This is harmless, but you may " + "want check your configuration and remove one CCPP if possible", configClass.getName())); } return configClass; } Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader)); if (logger.isTraceEnabled()) { logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s", configClass.getName(), enhancedClass.getName())); } return enhancedClass; }
Look at the createClass() method of this class:
private Class<?> createClass(Enhancer enhancer) { Class<?> subclass = enhancer.createClass(); // Registering callbacks statically (as opposed to thread-local) // is critical for usage in an OSGi environment (SPR-5932)... Enhancer.registerStaticCallbacks(subclass, CALLBACKS); return subclass; }
Callback using dynamic proxy:
// The callbacks to use. Note that these callbacks must be stateless. private static final Callback[] CALLBACKS = new Callback[] { new BeanMethodInterceptor(), new BeanFactoryAwareMethodInterceptor(), NoOp.INSTANCE }
BeanMethodInterceptor is an internal class of ConfigurationClassEnhancer. It will execute the intercept() method of BeanMethodInterceptor interceptor class:
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback { /** * Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the * existence of this bean object. * @throws Throwable as a catch-all for any exception that may be thrown when invoking the * super implementation of the proxied method i.e., the actual {@code @Bean} method */ @Override @Nullable public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs, MethodProxy cglibMethodProxy) throws Throwable { ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance); String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod); // Determine whether this bean is a scoped-proxy if (BeanAnnotationHelper.isScopedProxy(beanMethod)) { String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName); if (beanFactory.isCurrentlyInCreation(scopedBeanName)) { beanName = scopedBeanName; } } // To handle the case of an inter-bean method reference, we must explicitly check the // container for already cached instances. // First, check to see if the requested bean is a FactoryBean. If so, create a subclass // proxy that intercepts calls to getObject() and returns any cached bean instance. // This ensures that the semantics of calling a FactoryBean from within @Bean methods // is the same as that of referring to a FactoryBean within XML. See SPR-6602. if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) && factoryContainsBean(beanFactory, beanName)) { Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName); if (factoryBean instanceof ScopedProxyFactoryBean) { // Scoped proxy factory beans are a special case and should not be further proxied } else { // It is a candidate FactoryBean - go ahead with enhancement return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName); } } // If the method executed by the proxy object is the factory method of the Bean being created, execute the corresponding method to obtain the object as the Bean if (isCurrentlyInvokedFactoryMethod(beanMethod)) { // The factory is calling the bean method in order to instantiate and register the bean // (i.e. via a getBean() call) -> invoke the super implementation of the method to actually // create the bean instance. if (logger.isInfoEnabled() && BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) { logger.info(String.format("@Bean method %s.%s is non-static and returns an object " + "assignable to Spring's BeanFactoryPostProcessor interface. This will " + "result in a failure to process annotations such as @Autowired, " + "@Resource and @PostConstruct within the method's declaring " + "@Configuration class. Add the 'static' modifier to this method to avoid " + "these container lifecycle issues; see @Bean javadoc for complete details.", beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName())); } // Note that the proxy object passed in here is equivalent to the method of executing the target object return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs); } // If the method executed by the proxy object is not the method that is creating the Bean, get it directly from the Spring container according to the name of the method (getBean() method) return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName); }
Note: if there is a method with @ Bean annotation in the Configuration class with @ Configuration annotation, the method will be executed through the proxy object when instantiating and executing the invoke() method, for example:
@Configuration public class ConverterConfig { @Bean public UserService userService(){ kieasar(); return new UserService(); } @Bean public Kieasar kieasar() { return new Kieasar(); } }
In the above example, the proxy class of ConverterConfig will first judge whether it is the method to create a Bean when executing the userService() method. If so, create an instance of UserService, which is equivalent to the method to execute the target object, and enter cglibmethodproxy Invokesuper(), call the method of the proxied object.
Then, when the proxy object executes the kieasar() method in userService(), although the method is annotated with @ Bean, what is being executed is the userService() method that creates the userService instance, so the iscurrylinvokedfactorymethod () result is false, and the last line of resolveBeanReference() will be executed, and the getBean() method will be executed, Get the instance of the kieasar object in the container according to the name of the method.
However, when Spring creates a Kieasar Bean, if the currently executed method happens to be the kieasar() method (that is, the method of creating a Kieasar instance), the iscurrentyinvokedfactorymethod () result is true and cglibmethodproxy is executed Invokesuper(), the method of the proxied object.
Parsing flow chart of ConfigurationClassPostProcessor class:
Finally, summarize the parsing process of ConfigurationClassPostProcessor class:
1. When starting Spring, you need to pass in a ScanBean Class to ApplicationContext, which will be encapsulated into a BeanDefinition according to the ScanBean class. We call it the configuration class BeanDefinition.
2. The configurationclasspostprocessor will take out the configuration class BeanDefinition (ScanBean).
3. Construct a ConfigurationClassParser to resolve the configuration class BeanDefinition, and generate a configuration class object ConfigurationClass.
4. If the @ Component annotation exists on the configuration class, resolve the internal class in the configuration class (there is recursion here, if the internal class is also a configuration class).
5. If the @ PropertySource annotation exists on the configuration class, resolve the annotation, get the PropertySource object, and add it to the environment.
6. If the @ ComponentScan annotation exists on the configuration class, parse the annotation and scan it to get a series of BeanDefinition objects, Then judge whether these beandefinitions are also configuration classes. BeanDefinition (as long as the @ Component annotation exists, it is a configuration class, so basically all scanned out are configuration classes). If so, continue to parse the configuration class (there is also recursion), and the corresponding ConfigurationClass will be generated.
7. If the @ Import annotation exists on the configuration class, judge the type of the imported class:
- If it is an ImportSelector, call and execute the selectImports method to get the class name, and then parse the class as a configuration class (also recursive);
- If it is ImportBeanDefinitionRegistrar, an ImportBeanDefinitionRegistrar instance object is generated and added to the importBeanDefinitionRegistrars property in the configuration class object (ConfigurationClass);
8. If the @ ImportResource annotation exists on the configuration class, store the imported resource path in the importedResources attribute in the configuration class object.
9. If there are @ Bean methods in the configuration class, these methods will be encapsulated as BeanMethod objects and added to the beanMethods attribute in the configuration class object.
10. If the configuration class implements some interfaces, check whether the @ Bean annotation method is defined in these interfaces.
11. If the configuration class has a parent class, resolve the parent class as a configuration class.
12. The scanbean configuration class will correspond to a ConfigurationClass. At the same time, some other configurationclasses will be generated during the parsing process. Next, use the reader to further parse the ConfigurationClass:
- If ConfigurationClass is imported through @ Import annotation, generate a BeanDefinition for this class, parse @ Scope, @ Lazy and other annotation information on this class, and register BeanDefinition;
- If there are some beanmethods in the ConfigurationClass, that is, some @ beans are defined, resolve these @ beans, generate the corresponding BeanDefinition, and register;
- If some resource files are imported into ConfigurationClass, such as XX XML, then parse these XX XML file, get and register BeanDefinition;
- If some importbeandefinitionregistrars are imported into the ConfigurationClass, execute the corresponding registerBeanDefinitions to register the BeanDefinition.