Analyze the automatic assembly source code and understand the @ Import and other related source code (full text 3w words, super detail)

Posted by kutyadog on Fri, 17 Dec 2021 03:18:33 +0100

preface

When it comes to SpringBoot, we are certainly familiar with it. The automatic configuration makes it very cool for us to use in the development process. For example, the traditional mvc project needs to write a large lump in the xml file to start aop, while the SpringBoot project only needs to add the @ EnableAspectJAutoProxy annotation. In that sentence, we should not only be able to use, but also understand the principle, In this way, when it comes to high-level knowledge or we need to expand horizontally in the future, it will be much smoother.

What did SpringBoot do when it started?

Once upon a time, I wonder if the reader has thought about it. Why do we just write the following SpringBoot startup class, and all the classes annotated by @ Component, @ Configuration... In the project will be parsed as beans and injected into the IOC container, why? The following figure shows the startup class of a very simple SpringBoot project. A simple @ SpringBootApplication annotation helps us get everything done

@SpringBootApplication annotation structure

@Enable autoconfiguration: enable automatic assembly (not the core of this article)
@ComponentScan: resolve the annotated class annotated by @ Component and inject it into the IOC container as a Bean (which is configured with some filtering rules, which is not discussed in this article)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

@EnableAutoConfiguration, enable auto assembly

@AutoConfigurationPackage: which packages are used for automatic assembly (the specific scope of this automatic assembly is analyzed below)
@AutoConfigurationImportSelector.class: import the autoconfigurationimportselector component (where the component is resolved, please analyze below!!! If the reader is anxious, you can directly jump to the directory of this article: Probe into the source code of @ Import annotation on parsing startup class)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

@AutoConfigurationPackage (determine the scope of scanning components)

Import such a component of the Registrar

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

Role of Registrar: inject a BeanDefinition object (an object that encapsulates all kinds of information about beans) into the container, debug and take a look

  static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
        }
    }

We can see that our BeanDefinitionRegistry will record the BeanDefinition in bean form and register it in the BeanDefinitionRegistry. From the figure, we can see that our com.zzh.payment and the following sub package matching classes are injected into the IOC container as beans.

AutoConfigurationImportSelector <==> DeferredImportSelector

You can see that AutoConfigurationImportSelector is essentially a DeferredImportSelector, which implements the ImportSelector interface, The effect is almost the same as that of ImportSelector, which is to inject some BeanDefinition into the IOC container (note that only BeanDefinition is injected and no Bean is instantiated). As for the difference, we will analyze it in detail below. Let's think about two core issues

  1. How does the DeferredImportSelector know which classes of BeanDefinition to inject?
  2. How does DeferredImportSelector inject the BeanDefinition of these classes?

DeferredImportSelector source code began to interpret

emmm thought about how to write the following after a while. If you don't understand the source code of @ import first, you really can't explain the source code of DeferredImportSelector. Therefore, the source code part below is a little long, and this part will involve the source code part of spring. So if you want to know the source code of @ import, DeferredImportSelector, ImportSelector, ImportBeanDefinitionRegistrar and @ ComponentScan, just go to the Yellow Icon in debug. Spring will make some preparations before instantiating the Bean, Calling the post processor of BeanFactory (the function of these post processors is to obtain the corresponding BeanDefinition information by parsing @ Configuration, @ Component, @ Bean, @ import... And interfaces such as DeferredImport and ImportSelector... And then put all beandefinitions into a container called beanDefinitionMap. Finally, in this.finishBeanFactoryInitialization() Take out all beandefinitions in beanDefinitionMap and instantiate them (Bean)

debug comes to the invokeBeanFactoryPostProcessors () method, calls all kinds of postprocessor, if beanFactory is BeanDefinitionRegistry type, takes out one bean of ConfigurationClassBeanPostProcessor from beanFactory, then immediately calls it. (the meaning of this post processor, as its name implies, is the class related to initialization processing and configuration). Note that the code beanFactory.getBean(ppName,BeanDefinitionRegistryPostProcessor.class) involves the source code of Spring Ioc. Readers who have not read the source code of Ioc can simply understand that if there is no bean, create a bean and return, If there is no bean, a bean will be created first and then returned. In short, a bean with the corresponding name will be returned

It is worth mentioning that the order of calling the post processor is to first process the post processor that implements the PriorityOrdered interface, then process the post processor that implements the Ordered interface, and finally process the post processor that does not implement any interface. If beanFactory is of type ConfigurableListableBeanFactory, the post processor will still execute "PriorityOrdered" or "other"

Then debug goes to invokeBeanDefinitionRegistryPostProcessors() to processConfigBeanDefinitions() to see how spring parses our configuration class. Use our parser ConfigurationClassParser to parse the Yellow corresponding class in the figure The BeanDefinition information of (PaymentApplication, the startup class of the SpringBoot project). The specific parsing process of debug enters the parse () method

Study deferredImport and import source code entry

BeanDefinitionHolder can be parsed in two cases:

  1. Resolve classes injected by annotation (classes marked by @ import, @ Bean, @ configuration, @ RestController...)
  2. Parse the classes injected by auto assembly (the classes in spring.factories file under META-INF directory)
public void parse(Set<BeanDefinitionHolder> configCandidates) {
//Get configuration class
        Iterator var2 = configCandidates.iterator();

        while(var2.hasNext()) {
        //Get configuration class Holder
            BeanDefinitionHolder holder = (BeanDefinitionHolder)var2.next();
            //Obtain the BeanDefinition of the configuration class through the Holder
            BeanDefinition bd = holder.getBeanDefinition();
            try {
            //Parsing annotation injected classes
                if (bd instanceof AnnotatedBeanDefinition) {
                //Parsing configuration class, import, compentscan and other source codes are reflected in it
                    this.parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName());
                } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition)bd).hasBeanClass()) {
                    this.parse(((AbstractBeanDefinition)bd).getBeanClass(), holder.getBeanName());
                } else {
                    this.parse(bd.getBeanClassName(), holder.getBeanName());
                }
            } catch (BeanDefinitionStoreException var6) {
                throw var6;
            } catch (Throwable var7) {
                throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd.getBeanClassName() + "]", var7);
            }
        }
//Parsing classes injected by auto assembly
        this.deferredImportSelectorHandler.process();
    }

The comments are written in great detail. You can see that the BeanDefinition of the SpringBoot startup class PaymentApplication is of type annotatedbeandefinition. Then debug and enter this parse(((AnnotatedBeanDefinition)bd). getMetadata(), holder. Getbeanname()), start analyzing the source code of @ Import.

How does the parse method parse the BeanDefinition? (processConfigurationClass method)

The essence of the parse () method is the processConfigurationClass () method. The processConfigurationClass () method wraps our BeanDefinition into a ConfigurationClass object, and then comes to the processConfigurationClass method. Don't worry about debug ging. First analyze the general structure of the source code and take a look at the comments

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
//Determine whether configClass needs to skip parsing
        if (!this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            ConfigurationClass existingClass = (ConfigurationClass)this.configurationClasses.get(configClass);
            //If configClass already exists in configurationClasses
            if (existingClass != null) {
                if (configClass.isImported()) {
                    if (existingClass.isImported()) {
                    //When neither configClass nor existingClass is import ed, merge the existing configClass with the configClass in configurationClasses
                        existingClass.mergeImportedBy(configClass);
                    }

                    return;
                }
//configClass is not imported by import, but already exists in configurationClasses. Remove the current configClass
                this.configurationClasses.remove(configClass);
                this.knownSuperclasses.values().removeIf(configClass::equals);
            }

            ConfigurationClassParser.SourceClass sourceClass = this.asSourceClass(configClass, filter);

            do {
            //Really deal with the place and core of our configuration class
                sourceClass = this.doProcessConfigurationClass(configClass, sourceClass, filter);
            } while(sourceClass != null);
//Put configClass into configurationClasses and circle key points
            this.configurationClasses.put(configClass, configClass);
        }
    }

Following the main context, we find that spring will squeeze out our main startup class (PaymentApplication). As long as sourceclass! = null, this.doProcessConfigurationClass(configClass, sourceClass, filter) will be executed in a loop all the time, Finally, our configClass (PaymentApplication) is stored in the Map collection of configurationClasses, indicating that you have been parsed by me

The place where the configuration class is actually resolved (doProcessConfigurationClass)

doProcessConfigurationClass method source code

@Nullable
    protected final ConfigurationClassParser.SourceClass doProcessConfigurationClass(ConfigurationClass configClass, ConfigurationClassParser.SourceClass sourceClass, Predicate<String> filter) throws IOException {
        if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
            this.processMemberClasses(configClass, sourceClass, filter);
        }

        Iterator var4 = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class, PropertySource.class).iterator();

        AnnotationAttributes importResource;
        while(var4.hasNext()) {
            importResource = (AnnotationAttributes)var4.next();
            if (this.environment instanceof ConfigurableEnvironment) {
                this.processPropertySource(importResource);
            } else {
                this.logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment");
            }
        }

        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            Iterator var14 = componentScans.iterator();

            while(var14.hasNext()) {
                AnnotationAttributes componentScan = (AnnotationAttributes)var14.next();
                Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                Iterator var8 = scannedBeanDefinitions.iterator();

                while(var8.hasNext()) {
                    BeanDefinitionHolder holder = (BeanDefinitionHolder)var8.next();
                    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                    if (bdCand == null) {
                        bdCand = holder.getBeanDefinition();
                    }

                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                        this.parse(bdCand.getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }

        this.processImports(configClass, sourceClass, this.getImports(sourceClass), filter, true);
        importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
        if (importResource != null) {
            String[] resources = importResource.getStringArray("locations");
            Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
            String[] var20 = resources;
            int var22 = resources.length;

            for(int var23 = 0; var23 < var22; ++var23) {
                String resource = var20[var23];
                String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
                configClass.addImportedResource(resolvedResource, readerClass);
            }
        }

        Set<MethodMetadata> beanMethods = this.retrieveBeanMethodMetadata(sourceClass);
        Iterator var18 = beanMethods.iterator();

        while(var18.hasNext()) {
            MethodMetadata methodMetadata = (MethodMetadata)var18.next();
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

        this.processInterfaces(configClass, sourceClass);
        if (sourceClass.getMetadata().hasSuperClass()) {
            String superclass = sourceClass.getMetadata().getSuperClassName();
            if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
                this.knownSuperclasses.put(superclass, configClass);
                return sourceClass.getSuperClass();
            }
        }

        return null;
    }

Probe into the source code of @ ComponentScan annotation on startup class

Before parsing our main startup class, we will first Parse the @ ComponentScan annotation on the SpringBoot project startup class, and repeat the Parse method analyzed above according to the BeanDefinition of the class parsed to the @ Component annotated class.

You can see that all classes modified by @ Component, @ Configuration, @ RestController, @ Import... Annotations in my project have been scanned in by @ ComponentScan annotation, and then parsed one by one.

Parsing the @ Import annotation on the startup class and exploring the source code (processImports)

After parsing the @ ComponentScan annotation, we will parse our Import annotation. debug enters this processImports(). In fact, no matter what annotation is parsed, the same method will be executed in the end. Let's sell it here first

Don't worry about analyzing this The source code of processimports (), let's first pay attention to what are the input parameters in it, what fark? this.getImports(sourceClass)?????

Actually this Getimports (sourceclass) is to obtain the Import component imported above the main startup class, including the opening chapter( @EnableAutoConfiguration, enable auto assembly )The AutoConfigurationImportSelector class mentioned above is the core of automatic assembly. It turns out that you parse it here!!!!!!! o my gad

Compared with our startup class, is it a lot clearer in an instant

Now start to analyze the text and come to the processImports () method. The code here is related to the source code of @ Import, ImportSelector, ImportBeanDefinitionRegistrar and DeferredImportSelector. Don't worry about analyzing the source code. Let's go through the general context of the source code first

private void processImports(ConfigurationClass configClass, ConfigurationClassParser.SourceClass currentSourceClass, Collection<ConfigurationClassParser.SourceClass> importCandidates, Predicate<String> exclusionFilter, boolean checkForCircularImports) {
        if (!importCandidates.isEmpty()) {
            if (checkForCircularImports && this.isChainedImportOnStack(configClass)) {
                this.problemReporter.error(new ConfigurationClassParser.CircularImportProblem(configClass, this.importStack));
            } else {
                this.importStack.push(configClass);

                try {
                    Iterator var6 = importCandidates.iterator();

                    while(var6.hasNext()) {
                        ConfigurationClassParser.SourceClass candidate = (ConfigurationClassParser.SourceClass)var6.next();
                        Class candidateClass;
                        if (candidate.isAssignable(ImportSelector.class)) {
                            candidateClass = candidate.loadClass();
                            ImportSelector selector = (ImportSelector)ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry);
                            Predicate<String> selectorFilter = selector.getExclusionFilter();
                            if (selectorFilter != null) {
                                exclusionFilter = exclusionFilter.or(selectorFilter);
                            }

                            if (selector instanceof DeferredImportSelector) {
                                this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector)selector);
                            } else {
                                String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                                Collection<ConfigurationClassParser.SourceClass> importSourceClasses = this.asSourceClasses(importClassNames, exclusionFilter);
                                this.processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                            }
                        } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                            candidateClass = candidate.loadClass();
                            ImportBeanDefinitionRegistrar registrar = (ImportBeanDefinitionRegistrar)ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);
                            configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                        } else {
                            this.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                            this.processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
                        }
                    }
                } catch (BeanDefinitionStoreException var17) {
                    throw var17;
                } catch (Throwable var18) {
                    throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", var18);
                } finally {
                    this.importStack.pop();
                }
            }

        }
    }

Looking at the source code, we can find that the design idea of the source code is to traverse the components registered by the annotation on the main startup class one by one. If the component type is ImportSelector, obtain the import class name in the selectImports method, then encapsulate it into a SourceClass object, and then parse the SourceClass That is, execute the processImports (SourceClass) method, and then judge the SourceClass as shown in the figure below.

After entering the ImportSelector branch, you will continue to judge the component type. If it is of DeferredImportSelector type, you will perform the corresponding operation.

Then our debug goes to this deferredImportSelectorHandler. Handle () method, you can see that our AutoConfigurationImportSelector has been saved in deferredImportSelectors at this time, which is recorded in the small notebook

  public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
            ConfigurationClassParser.DeferredImportSelectorHolder holder = new ConfigurationClassParser.DeferredImportSelectorHolder(configClass, importSelector);
            if (this.deferredImportSelectors == null) {
                ConfigurationClassParser.DeferredImportSelectorGroupingHandler handler = ConfigurationClassParser.this.new DeferredImportSelectorGroupingHandler();
                handler.register(holder);
                handler.processGroupImports();
            } else {
            //Store the configuration class in deferredImportSelectors
                this.deferredImportSelectors.add(holder);
            }

        }


If it is a component of ImportSelector type, do the following

If it is of ImportBeanDefinitionRegistrar type, perform the following operations to store the Class of the component in a map of importBeanDefinitionRegistrars

If a normal type performs the following operations and returns to processConfigurationClass (), it will experience the parsing operations related to @ Component, @ Import, DeferredImportSelector, ImportSelector and ImportBeanDefinitionRegistrar for this class again. When will the cycle be terminated?

In fact, there is a prerequisite for parsing @ Component, @ Import, DeferredImportSelector, ImportSelector and ImportBeanDefinitionRegistrar of this class, that is sourceClass=null, but if it is an ordinary class, sourceClass=null, the loop will be terminated, and the configClass of this ordinary type will be directly stored in configurationClasses

Summary processImports() method

The processImports method will perform a series of recursive parsing operations on our class. If you find that you are a class of ImportSelector type, The class in your corresponding selectImport () method will be parsed. If there is a class of ImportSelector type in the selectImport () method, it will be parsed recursively until selectImport () method returns all ordinary classes. Finally, wrap the BeanDefinition of these ordinary classes into a configClass object and store it in the map of configurationClasses. However, at this time, the BeanDefinition of the automatically assembled class is not stored in configurationClasses, but in this.deferredImportSelectorHandler.process In (), the BeanDefinition of the automatically assembled class is stored in configurationClasses. The specific storage process is analyzed below.

After we resolve all the components registered in the startup class, we will eventually return null!!!!! Return null means that the current configClass has been resolved and can be stored in configurationClasses. (it indicates that the main startup class and all components on the main startup class have been resolved, and the corresponding configClass classification has been saved in different maps)

  • @The BeanDefinition of import, importSelector, common class and type is finally packaged and stored in the map named configurationClasses
  • The BeanDefinition of DeferredImportSelector type is finally packaged and stored in the map named deferredImportSelectors
  • The BeanDefinition of ImportBeanDefinitionRegistrar type is finally packaged and stored in a Map named importBeanDefinitionRegistrars

DeferredImportSelector source code analysis

To get back to the point, go back to our parse method and finally debug here. At this time, the BeanDefinition of the class injected by annotation has been parsed. (start parsing the automatically assembled class). Go in and have a look. The source code map is as follows

The blue arrow in the figure still skips....... I drew the wrong picture. I don't want to change it..... When parsing the BeanDefinition of the annotation injected class, the of the class of type DeferredImportSelector has been stored in deferredImportSelectors. So debug will come to handler Processgroupimports().

In fact, the processGroupImports is very simple. Resolve the DeferredImportSelector component contained in the group according to the corresponding gruop, Call the corresponding getImports () method (get some definition information of the class to be automatically assembled and encapsulated in the entry object), and then call the processImport () method again for each entry object. Finally, the BeanDefinition of the automatically assembled class will also be stored in configurationClasses. As for processImport The () method has been analyzed above. Now focus on the getImports () method in DeferredImportSelector.

How does the DeferredImportSelector know the BeanDefinition of which classes to assemble

Remember the two questions I asked at the beginning of the article, how does the DeferredImportSelector know which classes to assemble BeanDefinition? The answer is in the code in the figure below. Don't worry and start analysis step by step.

debug enter this group. In the process () method, the method is very simple. Encapsulate the classes that need to be automatically assembled into AutoConfigurationEntry objects and store them in autoConfigurationEntries. The problem arises. Where do you use this autoConfigurationEntries? getAutoConfigurationEntry()? Where do you get the classes that need to be automatically assembled?

The getAutoConfigurationEntry method doesn't look good. Just look at the following picture

Get the candidate classes to be automatically assembled, and click springfactoriesloader Loadfactorynames(), but pay attention to the input parameters here. Do you feel a little familiar with the following input parameters? It's EnableAutoConfiguration. Take a closer look at the following figures. I'm sure the readers will be suddenly enlightened

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        return configurations;
    }


The next step is to start from spring The factories file reads the value of the EnableAutoConfiguration node. These classes will be instantiated and injected into the IOC container when the IOC container is initialized later

You can see that our class b has been scanned in. Finally, the string "com.zzh.payment.testBean.b" will be encapsulated into an AutoConfigurationEntry object and stored in the map of autoConfigurationEntries. This is the answer to the question in this section.

How does DeferredImportSelector inject the BeanDefinition of these classes?

To get back to business, our first step has been completed and we have begun our second step.

The source code is as follows: selectImports. In the AutoConfigurationImportSelector class, the objects in autoConfigurationEntries are traversed one by one, and then encapsulated into an iterator return of an entry object

public Iterable<Entry> selectImports() {
        if (this.autoConfigurationEntries.isEmpty()) {
            return Collections.emptyList();
        } else {
            Set<String> allExclusions = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
            Set<String> processedConfigurations = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));
            processedConfigurations.removeAll(allExclusions);
            return (Iterable)this.sortAutoConfigurations(processedConfigurations, this.getAutoConfigurationMetadata()).stream().map((importClassName) -> {
                return new Entry((AnnotationMetadata)this.entries.get(importClassName), importClassName);
            }).collect(Collectors.toList());
        }
    }

Finally, processImport () is applied to each enrty object one by one. After calling b this class, it will be put into configurationClasses

I use a conditional breakpoint here. The condition is: ((classpathresource) configclass getResource()). getPath(). equals(“com/zzh/payment/testBean/b.class”)

Wait until all the classes that need to be automatically assembled are resolved, and then go to this reader. Loadbeandefinitions (configclasses) method, start to put all beandefinitions that need to be injected into the IOC container into the BeanDefinitionMap

Where does the data in BeanDefinitionMap come from? (loadBeanDefinitions)

Traverse the configClass in configurationClasses one by one, and then load it

  public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
        ConfigurationClassBeanDefinitionReader.TrackedConditionEvaluator trackedConditionEvaluator = new ConfigurationClassBeanDefinitionReader.TrackedConditionEvaluator();
        Iterator var3 = configurationModel.iterator();

        while(var3.hasNext()) {
            ConfigurationClass configClass = (ConfigurationClass)var3.next();
            this.loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
        }

    }

After the breakpoint enters the loadBeanDefinitions () method, the unimportant process in the middle is omitted first, and finally comes to the following figure. Of course, I use a conditional breakpoint ((classpathresource) configclass getResource()). getPath(). Equals ("com/zzh/payment/testBean/b.class")

Finally, sort out all kinds of class information, set the scope and name of the bean, and finally register the BeanDefinition

private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
        AnnotationMetadata metadata = configClass.getMetadata();
        AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);
        ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
        configBeanDef.setScope(scopeMetadata.getScopeName());
        String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
        AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
        definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        //Register BeanDefinition
        this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
        configClass.setBeanName(configBeanName);
        if (logger.isTraceEnabled()) {
            logger.trace("Registered bean definition for imported class '" + configBeanName + "'");
        }

    }

Then our class b is registered successfully. At this point, the whole process of automatic assembly has been completed

Personal feelings after reading spring source code

  1. The automatic assembly of benchmarking springboot project and xml is actually more like playing the role of xml, because it is not loaded based on annotation scanning, and it needs to rely on files.
  2. Strictly speaking, enabling automatic assembly does not start IOC injection into beans. Enabling automatic assembly only injects the BeanDefinition of the class to be injected into the IOC container into the BeanDefinitionMap. The real process from class to bean also needs to complete the instantiation in the bean life cycle
  3. The automatic assembly feels a bit like lazy loading. In fact, spring under META-INF directory The factories file has been scanned and parsed when the project is started and stored in the local cache (a cache map). When we start automatic assembly, we have priority to obtain the corresponding information from the cache
  4. If you want to really understand the source code and don't debug it yourself, you really know a little. This article only briefly describes the general parsing process of springBoot annotation on injection.
  5. After understanding the principle of automatic assembly, in fact, we know the working principle of the jar package that maven relies on, because when the springBoot project starts, it will automatically scan the project and all beans in the jar package, and then inject them into the IOC container. During our development, even if you have not configured relevant beans, But there are also default beans that can be used. The reason is that the beans configured in the jar package are automatically assembled into our IOC container. In fact, this is the core of the starter launcher

Where does the BeanDefinition required for Bean instantiation in the attached page come from?

Combined with my previous experience in reading the source code of spring IOC, I posted several pictures for your reference. It is found that the @ Import annotation is just preparing for bean instantiation




It's not easy to analyze the source code. Clicking one gives me the greatest motivation

Topics: Java Spring Spring Boot