ConfigurationClassParser
In the Configuration ClassPostProcessor#processConfigBeanDefinitions method, the Configuration ClassParser object is created and its parse method is called. This method is responsible for parsing configuration classes, scanning packages, and registering BeanDefinition. The source code is as follows:
//Configuration ClassParser#parseSet <Bean Definition Holder> Method Source public void parse(Set<BeanDefinitionHolder> configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { // Call different parse methods based on different BeanDefinition instance objects // The bottom layer is actually calling org. spring framework. context. annotation. Configuration ClassParser. process Configuration Class if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } } //Execute Deferred Import Selector this.deferredImportSelectorHandler.process(); }
Within this method, different parse methods are invoked according to different BeanDefinition instance objects, and the bottom layer of these parse methods is actually the Configuration ClassParser# processConfiguration Class method.
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { // Need to skip @Conditional if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } // At first entry, configurationClasses size = 0,existingClass must be null ConfigurationClass existingClass = this.configurationClasses.get(configClass); if (existingClass != null) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } // Otherwise ignore new imported config class; existing non-imported class overrides it. return; } else { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. this.configurationClasses.remove(configClass); this.knownSuperclasses.values().removeIf(configClass::equals); } } // Recursively process the configuration class and its superclass hierarchy. SourceClass sourceClass = asSourceClass(configClass); do { // Real analysis sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null); this.configurationClasses.put(configClass, configClass); }
The ConfigurationClass object passed in by the method is the encapsulation of the configuration class. First, determine whether there is an @Conditional annotation on the configuration class and whether it needs to skip parsing the configuration class.
Then, call doProcessConfiguration Class (configClass, sourceClass); do real parsing. Among them, configClass is the configuration class of the program, and sourceClass is created by configClass.
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { // @ Configuration inherits @Component if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // Recursively process any member (nested) classes first // Recursive processing of internal classes processMemberClasses(configClass, sourceClass); } // Process any @PropertySource annotations // Processing @PropertySource // @ PropertySource annotations are used to load properties files for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } // Process any @ComponentScan annotations 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(); } //Determine if there are configuration classes in the parsed retrieved BeanDefinition // The configuration classes here include Full Configuration Class and LiteConfiguration Class // That is, as long as you have one of the annotations in @Configuration, @Component, @ComponentScan, @Import, @ImportResource and @Bean. if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { //If you have a configuration class, call it recursively, parse the configuration class, the if is almost true, and the method is almost always executed. parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } // Process any @Import annotations processImports(configClass, sourceClass, getImports(sourceClass), true); // Process any @ImportResource annotations AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // Process individual @Bean methods //Method for handling a single @Bean Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces 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; }
Resolve internal classes
There is a @Configuration annotation on the configuration class, which inherits @Component, if judged to be true, calls the processMemberClasses method, and recursively parses the internal classes in the configuration class.
Resolve the @PropertySource annotation
If there is a @PropertySource annotation on the configuration class, the properties file is parsed and loaded, and the properties are added to the Spring context. ((Configurable Environment) this. environment). getPropertySources (). addFirstPropertySource (new Source);.
Processing @ComponentScan annotations
Get the @ComponentScan annotation on the configuration class to determine if skipping is necessary. Loop all ComponentScans and perform the scan immediately. The ComponentScan Annotation Parser parse method is as follows:
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) { // Create ClassPathBean Definition Scanner // A ClassPathBean Definition Scanner is also created in the Annotation Config Application Context constructor // This proves that the scanner execution is not in the constructor, but created here. ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); // @ Custom BeanName Generator can be registered in ComponentScan // Note, however, that as you can see from the source code, the custom BeanName Generator registered here is only valid for the current scanner Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator"); boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass); scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator : BeanUtils.instantiateClass(generatorClass)); ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy"); if (scopedProxyMode != ScopedProxyMode.DEFAULT) { scanner.setScopedProxyMode(scopedProxyMode); } else { Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver"); scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass)); } scanner.setResourcePattern(componentScan.getString("resourcePattern")); for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addIncludeFilter(typeFilter); } } for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) { for (TypeFilter typeFilter : typeFiltersFor(filter)) { scanner.addExcludeFilter(typeFilter); } } boolean lazyInit = componentScan.getBoolean("lazyInit"); if (lazyInit) { scanner.getBeanDefinitionDefaults().setLazyInit(true); } Set<String> basePackages = new LinkedHashSet<>(); String[] basePackagesArray = componentScan.getStringArray("basePackages"); for (String pkg : basePackagesArray) { String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); Collections.addAll(basePackages, tokenized); } // @ComponentScan(basePackageClasses = Xx.class) // basePackageClasses can be specified and can be scanned by Spring as long as they are packages and their subpackages that belong to these classes. // Often an empty class is used as the base Package Classes, defaulting to the package and its subpackages where the current configuration class is located. for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } if (basePackages.isEmpty()) { basePackages.add(ClassUtils.getPackageName(declaringClass)); } scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) { @Override protected boolean matchClassName(String className) { return declaringClass.equals(className); } }); //Executive scan return scanner.doScan(StringUtils.toStringArray(basePackages)); }
Pick out some places that I think are the key points to record:
- A new ClassPathBeanDefinitionScanner object is created in the parse method, and a ClassPathBeanDefinitionScanner object is created in the AnnotationConfigApplicationContext constructor, which confirms that within Spring, it is not the scanner in AnnotationConfigApplicationContext that actually performs the scan.
- As you can see from the source code, a custom BeanName Generator can be registered in @ComponentScan, and this BeanName Generator is only valid for the current scanner. That is to say, the BeanName Generator can only affect the BeanName generation rules of bean s under the scanner scanning path.
- Finally, call scanner. doScan (StringUtils. toStringArray (base Packages); the method performs a real scan, and the method returns the BeanDefinition obtained by the scan.
Verify that there are configuration classes in the obtained BeanDefinition
Check if there are configuration classes in the BeanDefinition scanned, and if there are configuration classes, the configuration classes here include Full Configuration Class and LiteConfiguration Class. (That is to say, as long as there is one annotation in @Configuration, @Component, @ComponentScan, @Import, @ImportResource and @Bean), the parse method is called recursively for parsing.
Resolve the @Import annotation
processImports(configClass, sourceClass, getImports(sourceClass), true);
The processImports method is responsible for parsing the @Import annotation. Configuration Class is a configuration class, and sourceClass is created by configClass. getImports(sourceClass) gets all @Import annotation information from sourceClass, and then calls Configuration ClassParser process Imports.
// Configuration ClassParser processImports source code private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) { if (importCandidates.isEmpty()) { return; } if (checkForCircularImports && isChainedImportOnStack(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { // Import Candidates is the encapsulation of @Import // Loop import Candidates to classify import content for (SourceClass candidate : importCandidates) { // import Import ImportSelector Interface Classes if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class<?> candidateClass = candidate.loadClass(); // Reflection creates instance objects of this class ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); //Is there an implementation of the relevant Aware interface, and if so, this calls the relevant method ParserStrategyUtils.invokeAwareMethods( selector, this.environment, this.resourceLoader, this.registry); // ImportSelector for Delayed Loading if (selector instanceof DeferredImportSelector) { // ImportSelector with delayed loading is first placed in List, and delayed loading this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else { // Ordinary ImportSelector, which executes its selectImports method, obtains a fully qualified array of class names for the classes that need to be imported String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames); // Recursive call processImports(configClass, currentSourceClass, importSourceClasses, false); } // Is it ImportBean Definition Registrar } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions Class<?> candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); ParserStrategyUtils.invokeAwareMethods( registrar, this.environment, this.resourceLoader, this.registry); // Add to the member variable org. spring framework. context. annotation. Configuration Class. importBeanDefinition Registrars configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class // Ordinary @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); // Resolve the imported @Configuration class processConfigurationClass(candidate.asConfigClass(configClass)); } } } 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(); } } }
Resolve the @ImportResource annotation
@ ImportResource annotations can be imported into xml configuration files.
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); } }
Resolving the @Bean method
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); }
Convert the @Bean method into a BeanMethod object and add it to the Configuration Class BeanMethods collection.
If there is a parent class, it resolves the parent class
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(); } }
If there is a parent class, it returns the parent Class object and continues to call the method. Until null is returned, the outer loop ends.
do { // Real analysis sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null);
Source Learning Notes: https://github.com/shenjianen...
Welcome to pay attention to the public number.