preface

In this section, learn how to handle the Spring Boot configuration class.

There are a lot of details. The more I see, the more I feel that my skills are not enough and I need more refueling.

Let’s start with an outline of the process. More on that later.

Configuration class resolution

Configure the class resolution entry

The entry of the configuration class parsing, the refresh () method of invokeBeanFactoryPostProcessors (the beanFactory) :

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    / /...}}Copy the code

Continue to enter the PostProcessorRegistrationDelegate # invokeBeanFactoryPostProcessors () method, with a piece of code like this:

if (beanFactory instanceof BeanDefinitionRegistry) {
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
             // General post-processor set initialization
            List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
            // Register handler set initialization
            List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
            // Iterate over all processors
            for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
                / / if implemented BeanDefinitionRegistryPostProcessor register BeanDefinition processing
                if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                    BeanDefinitionRegistryPostProcessor registryProcessor =
                            (BeanDefinitionRegistryPostProcessor) postProcessor;
                    registryProcessor.postProcessBeanDefinitionRegistry(registry);
                    registryProcessors.add(registryProcessor);
                }
                else{ regularPostProcessors.add(postProcessor); }}... }Copy the code

Here, the implementation of BeanDefinitionRegistryPostProcessor, made a special logic to handle, one of the implementation for the ConfigurationClassPostProcessor:

BeanFactoryPostProcessor is used to guide the processing of classes annotated by @Configuration. This post-processor is a priority. Because it is important that any @Bean-annotated method declared in an @Configuration annotated class must register its corresponding Bean definition before any other BeanFactoryPostProcessor can be executed.

We entered the ConfigurationClassPostProcessor method is called:

    @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);
        // Call this method primarily
        processConfigBeanDefinitions(registry);
    }
Copy the code

processConfigBeanDefinitions

Continue into this method:

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
        String[] candidateNames = registry.getBeanDefinitionNames();
 
        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if(ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {... }// Check whether it is a configuration class. If so, set BeanDefiniton's attribute lite/full.
             // @Configuration --> full
             @component, @componentScan, @import, @importResource --> lite
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(newBeanDefinitionHolder(beanDef, beanName)); }}// Return immediately if no @Configuration classes were found
         // If the @Configuration class is not found, return immediately
        if (configCandidates.isEmpty()) {
            return; }...// 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(CONFIGURATION_BEAN_NAME_GENERATOR); . }}...// Parse each @Configuration class
        // Parse each @Configuration class
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment,
                this.resourceLoader, this.componentScanBeanNameGenerator, registry);
 
        // BeanDefiniton candidate set
        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
        // The BeanDefiniton set has been resolved
        Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
        do {
             // Resolve @Component, @propertysources, @ComponentScans, @importResource, @bean in the configuration class.
             // Only @Configuration and @Componetscans will be loaded as BeanDefinition
            parser.parse(candidates);
            parser.validate();
 
            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());
            }
            / / parser. Parse (candidates) parsing out these beans may introduce new bean, for example implements ImportBeanDefinitionRegistrar or bean ImportSelector interface, Or the bean has a method annotated by @bean.
            // So loadBeanDefinition() is executed once. Used to resolve ImportBeanDefinitionRegistrar ImportSelector interface or @ Bean annotation methods.
            this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); candidates.clear(); . }Copy the code

parser.parse(candidates)

This method is the core method for parsing configuration classes.

// ConfigurationClassParser # parse
public void parse(Set<BeanDefinitionHolder> configCandidates) {
        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                // Identify the configuration class by annotating it into this method.
                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()); }}... }Copy the code

Enter overloaded methods:

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
        processConfigurationClass(new ConfigurationClass(metadata, beanName));
    }
Copy the code

processConfigurationClass():

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        // Determine if parsing conditions are met
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            return; }...// Recursively process the configuration class and its superclass hierarchy.
        // Recursively process configuration classes and their superclass hierarchies.
        / / equal to say doProcessConfigurationClass logic is the core of the processing load Configuration recursion.
        SourceClass sourceClass = asSourceClass(configClass);
        do {
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while(sourceClass ! =null);
 
        this.configurationClasses.put(configClass, configClass);
    }
Copy the code

doProcessConfigurationClass():

Apply processing and build a complete ConfigurationClass by reading annotations, members, and methods from the source class.

Good! From here, we can think of a Configuration class being parsed.

Let’s look at the parsing steps:

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
            throws IOException {
 
        if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
            // Recursively process any member (nested) classes first
            // 1. Handle member variables first. If there are configuration class members, will processConfigurationClass. So you get recursion.
            processMemberClasses(configClass, sourceClass);
        }
 
        // Process any @PropertySource annotations
         // 2. Handle @propertysource, which loads properties into the environment
        for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), PropertySources.class,
                org.springframework.context.annotation.PropertySource.class)) {
            if (this.environment instanceofConfigurableEnvironment) { processPropertySource(propertySource); }... }// Process any @ComponentScan annotations
         // 3. Handle @ComponentScan annotations. Scan beans in the scan path configured on the configuration class.
        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
                / / scanning @ ComponentScan
                // This method scans the configured scan range. In fact, when we started, we loaded our bootstrap class. Then go to scan all the subpackages of the boot class package.
                // Then the other Configuration we wrote is scanned and loaded in.
                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
                 // Check the set of scan definitions for any other configuration classes and recursively resolve as needed
                for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                    if (bdCand == null) {
                        bdCand = holder.getBeanDefinition();
                    }
                    // If Configuration is displayed, parse recursively
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); }}}}// Process any @Import annotations
         // 4. Handle the @import annotation. The configuration class introduced via the @import annotation.
        // It is also important to note that the autowire entry is @import.
        processImports(configClass, sourceClass, getImports(sourceClass), true);
 
        // Process any @ImportResource annotations
        // 5. Handle the @importResource annotation and parse the configuration file.
        // Note here that the processing is addImportedResource. That is, only the configClass properties are assigned and nothing else is done.
        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
        // 6. Handle methods annotated by @bean
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }
 
        // Process default methods on interfaces
         // 7. The default method for handling interfaces
        processInterfaces(configClass, sourceClass);
 
        // Process superclass, if any
        // 8. Classes, except those whose package names start with Java
        if (sourceClass.getMetadata().hasSuperClass()) {
            String superclass = sourceClass.getMetadata().getSuperClassName();
            if(superclass ! =null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {
                this.knownSuperclasses.put(superclass, configClass);
                // Superclass found, return its annotation metadata and recurse
                returnsourceClass.getSuperClass(); }}// No superclass -> processing is complete
        return null;
    }
Copy the code

This method resolves the core methods of the configuration class, which resolve @Component, @propertysources, @ComponentScans, @ImportResource, @Bean, as well as the default methods provided by Java8.

These methods are not a close look, the first time to see the source code, or the process to understand the speaker.

conclusion

This section Outlines the main processing flow of the Configuration class.

There are a lot of details I’ll leave to learn.