Note: This series of source code analysis is based on SpringBoot 2.2.2.RELEASE, the corresponding Spring version is 5.2.2.RELEASE, the source code of the Gitee repository repository address: funcy/ Spring-boot.

Annotations (a) the condition of springboot automatic assembly of the article, based on the analysis of the @ ConditionalOnBean / @ ConditionalOnMissingBean annotation condition judgment, the official is strongly recommended that we used in the automatic assembly class these two annotations, And the @conditionalonBean / @conditionalonmissingBean class is initialized after the specified class, so how does SpringBoot control the auto-assembly order? This paper will study it in the future.

1. Springboot handles the process of auto-assembly classes

To be clear, the auto-assembly order discussed in this article refers to the order in which classes are registered with the beanFactory. The general process of SpringBoot processing auto-assembly classes is as follows:

  1. Loading autoassembly classes, as analyzed in springBoot autoassembly loading autoassembly classes;
  2. Sort the auto-assembly class, which will be analyzed in this paper;
  3. Iterate through the autowiring classes and perform the following operations for each autowiring class one by one:
    1. Judge whether the current automatic assembly class meets the assembly conditions according to the condition annotations;
    2. If the current autowiring class is full of assembly conditions, register tobeanFactoryIn the.

ConditionalOnBean/ ConditionalOnMissingBean for the following two auto-assembly classes:

// A is for autowiring
@Configuration
public class A {
    @Bean
    @ConditionalOnMissingBean("b1")
    public A1 a1(a) {
        return newA1(); }}// B is the auto-assembly class
@Configuration
public class B {
    @Bean
    public B1 b1(a) {
        return newb1(); }}Copy the code

A1 and b1 are initialized in two different autowiring classes, and a1 will be initialized only if b1 does not exist. According to the steps summarized in the springboot autowiring classes above, we only need to specify that b1 is initialized before a1, so there will be no exceptions.

So, how do you specify the order of the autowiring classes?

2. Auto-assemble class sequence control annotations

Springboot provides us with two sorting methods for autowiring classes:

  • Absolute automatic assembly sequence@AutoConfigOrder
  • Relative automatic assembly sequence —@AutoConfigureBefore@AutoConfigureAfter

@AutoConfigUreBefore and @AutoConfigureAfter specify the class. Indicates which class is assembled before or after.

Returning to the example, we can specify the assembly order as follows:

// A is for autowiring
@Configuration
// Automatic assembly after b.lass
@AutoConfigureAfter(B.class)
public class A {
    @Bean
    @ConditionalOnMissingBean("b1")
    public A1 a1(a) {... }}// B is the auto-assembly class
@Configuration
public class B {... }Copy the code

3. Sort the autowiring classes

As mentioned earlier, @AutoConfigOrder, @AutoConfigureBefore, and @AutoConfigureAfter control the assembly order of the autoloassembly classes. Where are they sorted? In springBoot autowiring load autowiring class, we summarized the steps of obtaining autowiring class in 6 steps:

  1. callAutoConfigurationImportSelector#getAutoConfigurationEntry(...)Method to load the automatic assembly class.
  2. Save the resulting autowiring class toautoConfigurationEntries;
  3. You get filter classes, and those filter classes are defined by@EnableAutoConfigurationtheexcludeorexcludeNameThe specified;
  4. willautoConfigurationEntriesconvertLinkedHashSet, the results forprocessedConfigurations;
  5. Get rid ofprocessedConfigurationsClasses to filter;
  6. After sorting the classes obtained in step 5, return.

For automatic assembly in step 6 in the class, the corresponding method is AutoConfigurationImportSelector AutoConfigurationGroup# sortAutoConfigurations, code is as follows:

private List<String> sortAutoConfigurations(Set
       
         configurations, AutoConfigurationMetadata autoConfigurationMetadata)
        {
    // Create an AutoConfigurationSorter object
    / / and then call AutoConfigurationSorter. GetInPriorityOrder sort
    return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)
            .getInPriorityOrder(configurations);
}
Copy the code

This method is handled in two steps:

  1. To create theAutoConfigurationSorterobject
  2. callAutoConfigurationSorter.getInPriorityOrdersorting

Let’s start with the AutoConfigurationSorter creation:

class AutoConfigurationSorter {

    private final MetadataReaderFactory metadataReaderFactory;

    private final AutoConfigurationMetadata autoConfigurationMetadata;

    The /** * constructor * simply assigns values to the parameters passed in, assigning them to member variables */
    AutoConfigurationSorter(MetadataReaderFactory metadataReaderFactory,
            AutoConfigurationMetadata autoConfigurationMetadata) {
        Assert.notNull(metadataReaderFactory, "MetadataReaderFactory must not be null");
        this.metadataReaderFactory = metadataReaderFactory;
        this.autoConfigurationMetadata = autoConfigurationMetadata; }... }Copy the code

As you can see, the structure of the AutoConfigurationSorter method didn’t do anything of real substance in operation, it seems sort of key still depends on AutoConfigurationSorter. GetInPriorityOrder method, the method of code is as follows:

List<String> getInPriorityOrder(Collection<String> classNames) {
    // 1. Wrap classNames as AutoConfigurationClasses
    AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
            this.autoConfigurationMetadata, classNames);
    List<String> orderedClassNames = new ArrayList<>(classNames);
    // sort by class name
    Collections.sort(orderedClassNames);
    // 3. Sort with @autoconfigureOrder
    orderedClassNames.sort((o1, o2) -> {
        int i1 = classes.get(o1).getOrder();
        int i2 = classes.get(o2).getOrder();
        return Integer.compare(i1, i2);
    });
    // 4. Sort by @autoConfigureBefore, @autoConfigureAfter
    orderedClassNames = sortByAnnotation(classes, orderedClassNames);
    return orderedClassNames;
}
Copy the code

From the code, this method performs the following steps:

  1. willclassNamesPackaged inAutoConfigurationClasses
  2. Sort by class name
  3. use@AutoConfigureOrderThe sorting
  4. use@AutoConfigureBefore.@AutoConfigureAfterThe sorting

OrderedClassNames = orderedClassNames; orderedClassNames = orderedClassNames; orderedClassNames = orderedClassNames; If @AutoConfigureOrder, @AutoConfigureBefore, and so on are not specified, sorting is done using the class name.

Let’s look at these operations in detail.

4. WillclassNamesPackaged inAutoConfigurationClasses

. This operation is located in AutoConfigurationSorter AutoConfigurationClasses# AutoConfigurationClasses method, the code is as follows:

private static class AutoConfigurationClasses {

    // Save the result
    private final Map<String, AutoConfigurationClass> classes = new HashMap<>();

    /** * constructor */
    AutoConfigurationClasses(MetadataReaderFactory metadataReaderFactory,
            AutoConfigurationMetadata autoConfigurationMetadata, Collection<String> classNames) {
        // Make a method call
        addToClasses(metadataReaderFactory, autoConfigurationMetadata, classNames, true);
    }

    /** * To add a class, wrap it as an AutoConfigurationClass and add it to a Map called classes
    private void addToClasses(MetadataReaderFactory metadataReaderFactory,
            AutoConfigurationMetadata autoConfigurationMetadata, Collection<String> classNames, 
            boolean required) {
        for (String className : classNames) {
            if (!this.classes.containsKey(className)) {
                // Wrap className as AutoConfigurationClass
                AutoConfigurationClass autoConfigurationClass = new AutoConfigurationClass(
                        className, metadataReaderFactory, autoConfigurationMetadata);
                boolean available = autoConfigurationClass.isAvailable();
                // The @AutoConfigureBefore and @AutoConfigureAfter classes are required to be false
                if (required || available) {
                    this.classes.put(className, autoConfigurationClass);
                }
                if (available) {
                    // recursive call
                    addToClasses(metadataReaderFactory, autoConfigurationMetadata,
                            autoConfigurationClass.getBefore(), false);
                    addToClasses(metadataReaderFactory, autoConfigurationMetadata,
                            autoConfigurationClass.getAfter(), false); }}}}... }Copy the code

From the code above,

  • AutoConfigurationClassesContains a member variable:classesThe type isMap.keyString(i.e.className),valueAutoConfigurationClass(i.e.classNameContain class);
  • AutoConfigurationClassesThe constructor is calledaddToClasses(...)The method will iterate over the incomingclassNamesAnd wrap it intoAutoConfigurationClassThen save toclassesIn the.

Based on the analysis of addToClasses (…). AutoConfigurationClass configurationClass configurationClass

As you can see, AutoConfigurationClass is a wrapper around the class name and also holds the classes specified by @AutoConfigureBefore and @AutoConfigureAfter. And some methods related to @AutoConfigureOrder, @AutoConfigureBefore, and @AutoConfigureAfter.

Let’s go back to addToClasses(…) The execution flow of this method is as follows:

  1. Iterate over the incomingclassNamesFor each of themclassName, perform the following operations;
  2. createAutoConfigurationClassAnd the incomingclassName;
  3. callAutoConfigurationSorter.AutoConfigurationClass#isAvailableMethod, getavailable;
  4. judgeavailablerequiredIf one of the values is true, add it toclasses;
  5. ifavailablefortrue, recursive processingclassNameby@AutoConfigureBeforewith@AutoConfigureAfterClass specified.

The process looks not complicated, but there are a few to need to analyze:

  1. AutoConfigurationSorter.AutoConfigurationClass#isAvailable: Judge the presentclassIf there is a
  2. AutoConfigurationSorter.AutoConfigurationClass#getBefore: getclass: the currentclassNeed to be in theseclassBefore processing
  3. AutoConfigurationSorter.AutoConfigurationClass#getAfter: getclass: the currentclassNeed to be in theseclassAfter processing

Let’s take a look at each one.

4.1 AutoConfigurationSorter.AutoConfigurationClass#isAvailable

This method is used to determine if the current class is in the current project’s classpath.

boolean isAvailable(a) {
    try {
        if(! wasProcessed()) { getAnnotationMetadata(); }return true;
    }
    catch (Exception ex) {
        return false; }}Copy the code

This method doesn’t have much code; it calls wasProcessed() and then getAnnotationMetadata(). Note that getAnnotationMetadata() may throw an exception, and the recruitment exception will return false.

We continue to follow up AutoConfigurationSorter. AutoConfigurationClass# wasProcessed method:

private boolean wasProcessed(a) {
    return (this.autoConfigurationMetadata ! =null
        // Check whether the configuration exists in meta-INF /spring-autoconfigure-metadata.properties
        && this.autoConfigurationMetadata.wasProcessed(this.className));
}
Copy the code

In this method calls the AutoConfigurationMetadataLoader. PropertiesAutoConfigurationMetadata# wasProcessed method to determine:

@Override
public boolean wasProcessed(String className) {
    // Check whether there is a corresponding className for properties
    return this.properties.containsKey(className);
}
Copy the code

Properties (META-INF/spring-autoconfigure-metadata.properties);

Note that this file does not exist in the source code, it is written at compile time, about the file to write, load into the properties process, this article will not expand the analysis, here provides a general idea:

  • Writing a file: When the code is compiled, SpringBoot will automatically assemble some information about the class (for example, the class specified by @conditionalonClass, the bean specified by @conditionalonBean, @autoconfigureBefore, @autoconfigureAfter, etc.) to meta-INF /spring-autoconfigure-metadata.properties For AutoConfigureAnnotationProcessor processing class, this class is javax.mail annotation. Processing. AbstractProcessor subclasses, and AbstractProcessor provided by the JDK, Annotations can be processed at compile time;

  • File loading: In AutoConfigurationImportSelector. AutoConfigurationGroup# process method called AutoConfigurationImportSelector# getAutoConfiguration Entry, will be introduced to AutoConfigurationMetadata, The file meta-inf/spring – autoconfigure – metadata. Properties The content is loaded from here to AutoConfigurationMetadataLoader. PropertiesAutoConfigurationMetadata# properties of;

From these we can see, AutoConfigurationMetadataLoader. PropertiesAutoConfigurationMetadata# wasProcessed method is in fact judgment Meta-inf /spring-autoconfigure-metadata.properties whether there is a className configuration in the file.

Let’s go back to AutoConfigurationSorter. AutoConfigurationClass# isAvailable, take a look at another way: GetAnnotationMetadata (), the method in AutoConfigurationSorter. AutoConfigurationClass, the code is as follows:

private AnnotationMetadata getAnnotationMetadata(a) {
    if (this.annotationMetadata == null) {
        try {
            // Load the resource corresponding to 'className'. If the resource corresponding to className does not exist, an exception will be thrown
            MetadataReader metadataReader = this.metadataReaderFactory
                    .getMetadataReader(this.className);
            this.annotationMetadata = metadataReader.getAnnotationMetadata();
        }
        catch (IOException ex) {
            throw new IllegalStateException(...);
        }
    }
    return this.annotationMetadata;
}
Copy the code

Continue to enter the SimpleMetadataReaderFactory# getMetadataReader (String) :

@Override
IOException = IOException; IOException = IOException; IOException = IOException
public MetadataReader getMetadataReader(String className) throws IOException {
    try {
        / / conversion name: "the classpath: / / XXX XXX XXX. Class"
        String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX 
                + lassUtils.convertClassNameToResourcePath(className) 
                + ClassUtils.CLASS_FILE_SUFFIX;
        // Get resources. The default resourceLoader is classLoader
        Resource resource = this.resourceLoader.getResource(resourcePath);
        // Convert the resource to a MetadataReader object. If it does not exist, IOException will be thrown
        return getMetadataReader(resource);
    }
    catch (FileNotFoundException ex) {
        // It can be an inner class
        int lastDotIndex = className.lastIndexOf('. ');
        if(lastDotIndex ! = -1) {
            String innerClassName = className.substring(0, lastDotIndex) + '$' 
                    + className.substring(lastDotIndex + 1);
            Classpath: XXX/XXX $xxx.class"
            String innerClassResourcePath = ResourceLoader.CLASSPATH_URL_PREFIX 
                    + ClassUtils.convertClassNameToResourcePath(innerClassName) 
                    + ClassUtils.CLASS_FILE_SUFFIX;
            Resource innerClassResource = this.resourceLoader.getResource(innerClassResourcePath);
            IOException = IOException; IOException = IOException
            if (innerClassResource.exists()) {
                returngetMetadataReader(innerClassResource); }}throwex; }}Copy the code

The process for this method is as follows:

  1. Will the incomingclassNameconvertclasspath:xxx/xxx/Xxx.classThen load the corresponding resource, if the resource does not exist that isclassNameThe corresponding.classIf the file does not exist, an exception is thrown.
  2. In abnormalcatchBlock in order to preventclassNameIt’s an inner class that willclassNameconvertclasspath:xxx/Xxx$Xxx.classThen load the resource again, if the resource exists, directly return, otherwise the exception is thrown outward;

GetAnnotationMetadata () is used to determine whether the. Class corresponding to the current className exists in the project’s classpath path.

We summarize the two methods as follows:

  • AutoConfigurationSorter.AutoConfigurationClass#wasProcessed: the currentclassNameWhether inMETA-INF/spring-autoconfigure-metadata.propertiesIn the file
  • AutoConfigurationSorter.AutoConfigurationClass#isAvailable: the currentclassNameThe corresponding.classWhether the file exists

The final conclusion: AutoConfigurationSorter AutoConfigurationClass# isAvailable is used to determine the current className corresponding. Class files in the classpath path of the project.

4.2 AutoConfigurationSorter.AutoConfigurationClass#getBefore/getAfter

. Let’s look at the next AutoConfigurationSorter AutoConfigurationClass class of two methods: getAfter () and getBefore () :

Set<String> getBefore(a) {
    if (this.before == null) {
        this.before = (wasProcessed() 
            // Get the value directly if it exists in 'meta-INF /spring-autoconfigure-metadata.properties' file
            ? this.autoConfigurationMetadata.getSet(this.className, "AutoConfigureBefore", 
                    Collections.emptySet()) 
            Otherwise, get it from the @autoConfigureBefore annotation
            : getAnnotationValue(AutoConfigureBefore.class));
    }
    return this.before;
}

Set<String> getAfter(a) {
    if (this.after == null) {
        this.after = (wasProcessed() 
            // Get the value directly if it exists in 'meta-INF /spring-autoconfigure-metadata.properties' file
            ? this.autoConfigurationMetadata.getSet(this.className, "AutoConfigureAfter", 
                    Collections.emptySet()) 
            // Otherwise get it from the @AutoConfigureAfter annotation
            : getAnnotationValue(AutoConfigureAfter.class));
    }
    return this.after;
}

/ * * * from@AutoConfigureBefore/@AutoConfigureAfterGet values in annotations: value and name specify values */
private Set<String> getAnnotationValue(Class
        annotation) {
    Map<String, Object> attributes = getAnnotationMetadata()
            .getAnnotationAttributes(annotation.getName(), true);
    if (attributes == null) {
        return Collections.emptySet();
    }
    Set<String> value = new LinkedHashSet<>();
    Collections.addAll(value, (String[]) attributes.get("value"));
    Collections.addAll(value, (String[]) attributes.get("name"));
    return value;
}
Copy the code

The code form of the two methods is basically the same, so let’s look at the flow of getBefore() :

  1. If the current className exists in meta-INF /spring-autoconfigure-metadata.properties file, it can be specified directly. Some annotated information is written to the meta-INF /spring-autoconfigure-metadata.properties file;

  2. If step 1 fails, the value is taken from @autoConfigurebefore of the current class.

The flow of getAfter() is basically the same as that of getBefore() without analysis.

5. Use@AutoConfigureOrderThe sorting

Let’s go back to the AutoConfigurationSorter#getInPriorityOrder method and look at the sorting process for @autoconfigureorder:

List<String> getInPriorityOrder(Collection<String> classNames) {... orderedClassNames.sort((o1, o2) -> {int i1 = classes.get(o1).getOrder();
        int i2 = classes.get(o2).getOrder();
        returnInteger.compare(i1, i2); }); . }Copy the code

This sort operation uses List#sort, sort(…) The “Comparator” parameter specifies a collation rule. From the code point of view, getOrder() gets the order of the current class, and then uses Integer’s comparison rules to sort, so getOrder() is the key to sorting. Its approach is to just by AutoConfigurationSorter. AutoConfigurationClass# getOrder, code is as follows:

private int getOrder(a) {
    // Check whether the current className exists in meta-INF /spring-autoconfigure-metadata.properties
    if (wasProcessed()) {
        // If so, use the order specified in the file, otherwise use the default order
        return this.autoConfigurationMetadata.getInteger(this.className, 
                "AutoConfigureOrder", AutoConfigureOrder.DEFAULT_ORDER);
    }
    // Handle non-existent cases: Get the order specified by the @AutoConfigureOrder annotation
    Map<String, Object> attributes = getAnnotationMetadata()
            .getAnnotationAttributes(AutoConfigureOrder.class.getName());
    // If @autoConfigureOrder is not configured, the default order is used
    return(attributes ! =null)? (Integer) attributes.get("value") 
            : AutoConfigureOrder.DEFAULT_ORDER;
}
Copy the code

Get the order specified by the @AutoConfigureOrder annotation. If there is no @AutoConfigureOrder annotation, the default order is used. Default order AutoConfigureOrder. DEFAULT_ORDER a value of 0.

6. Use@AutoConfigureBefore.@AutoConfigureAfterThe sorting

The most exciting sorting of @autoconfigurebefore and @autoconfigureafter annotations is called AutoConfigurationSorter#sortByAnnotation:

/** ** * this method is just preparing some data, doSortByAfterAnnotation(...) * /
private List<String> sortByAnnotation(AutoConfigurationClasses classes, List<String> classNames) {
    // The className to be sorted
    List<String> toSort = new ArrayList<>(classNames);
    toSort.addAll(classes.getAllNames());
    // Sorted className
    Set<String> sorted = new LinkedHashSet<>();
    // The className is being sorted
    Set<String> processing = new LinkedHashSet<>();
    while(! toSort.isEmpty()) {// The real sorting method
        doSortByAfterAnnotation(classes, toSort, sorted, processing, null);
    }
    // Elements that are in the collection sorted but not in classNames are removed
    sorted.retainAll(classNames);
    return new ArrayList<>(sorted);
}

/ * * / * * specific sorting method
private void doSortByAfterAnnotation(AutoConfigurationClasses classes, List
       
         toSort, Set
        
          sorted, Set
         
           processing, String current)
         
        
        {
    if (current == null) {
        current = toSort.remove(0);
    }
    // Use Processing to determine if there is A loop comparison, for example, class A after class B, and class B after class A
    processing.add(current);
    / / classes. GetClassesRequestedAfter: current className need to perform after which the className
    for(String after : classes.getClassesRequestedAfter(current)) { Assert.state(! processing.contains(after),"AutoConfigure cycle detected between " + current + " and " + after);
        if(! sorted.contains(after) && toSort.contains(after)) {// recursive call
            doSortByAfterAnnotation(classes, toSort, sorted, processing, after);
        }
    }
    processing.remove(current);
    // Add to the sorted result
    sorted.add(current);
}
Copy the code

AutoConfigurationSorter#sortByAnnotation provides the structure to hold the data, and AutoConfigurationSorter#doSortByAfterAnnotation is the way to actually do sorting, Sorting operation is not very easy to understand, the general process is as follows:

  1. Find out which classnames need to be assembled after the current className and save them as afterClasses. That is, each className in afterClasses needs to be assembled before the current className.

  2. After iterating through afterClasses, continue to find afterClasses for each className, and so on recursively, without considering loop comparisons, there must eventually be a className whose afterClasses are empty. Here we add className to the sorted structure.

We take a look at get afterClasses methods of operation, to AutoConfigurationSorter AutoConfigurationClasses# getClassesRequestedAfter, code is as follows:

Set<String> getClassesRequestedAfter(String className) {
    // Current class: To get what class is executed after, get the class specified by the @AutoConfigureAfter annotation
    Set<String> classesRequestedAfter = new LinkedHashSet<>(get(className).getAfter());
    // Other classes: in the class that needs to be preceded
    this.classes.forEach((name, autoConfigurationClass) -> {
        if(autoConfigurationClass.getBefore().contains(className)) { classesRequestedAfter.add(name); }});return classesRequestedAfter;
}
Copy the code

From the point of view of the code, afterClasses contains two things:

  • Getting assembly after which class assembly is complete is getting@AutoConfigureAfterAnnotate the specified class
  • Gets which classes need to be assembled before the current class is assembled

7. Age: 43@ConditionalOnBean/@ConditionalOnMissingBean

ConditionalOnBean/ @conditionalonmissingBean pit mentioned above, it’s easy to avoid these pits once you know the order of auto-assembly:

  1. twobeanAll are automatic assembly type: pit avoidance mode is, use@AutoConfigureBefore / @AutoConfigureAfter@AutoConfigureOrderSpecifies the order of conditions, ensuring that conditions are annotatedbeanAssembly can be first;
  2. One is ordinaryspring beanOne is the autowiring class: if conditional annotationbeanIs a normal Spring bean, and the other is an autowiring class, in which case there is no processing, and the autowiring class isDeferredImportSelectorA subclass of innate autowiring classes in commonspring beanSubsequent processing; In contrast, conditional annotationsbeanAuto assembly class, and generalspring bean, this will definitely go wrong, do not use;
  3. Both are ordinaryspring bean: There is no pit avoidance method.spring beanRegistered tobeanFactoryThe sequence is not controllable and is not recommended in this case;

8. To summarize

This paper summarizes the assembly sequence of automatic assembly class and mainly introduces the following contents:

  1. Sort the autowiring classes:AutoConfigurationImportSelector.AutoConfigurationGroup#sortAutoConfigurations
  2. Specify the assembly order of the autowiring classes: use@AutoConfigureBefore / @AutoConfigureAfter@AutoConfigureOrder
  3. There are three kinds of sorting, in order:
    1. Sort by classNameStringProvide collation rules
    2. According to the@AutoConfigureOrderSpecifies the value to sort byIntegerProvide collation rules
    3. According to the@AutoConfigureBefore / @AutoConfigureAftersorting

    It should be noted that the above three sorting methods are carried out successively, and the result of the last sorting is the final order

  4. about@ConditionalOnBean/@ConditionalOnMissingBeanPothole Avoidance Guide:
    1. twobeanAll are automatic assembly type: pit avoidance mode is, use@AutoConfigureBefore / @AutoConfigureAfter@AutoConfigureOrderSpecifies the order of conditions, ensuring that conditions are annotatedbeanAssembly can be first;
    2. One is ordinaryspring beanOne is the autowiring class: conditional annotationsbeanMust be commonspring bean;
    3. Other situations are uncontrollable and not recommended.

Link to the original article:My.oschina.net/funcy/blog/…, limited to the author’s personal level, there are inevitable mistakes in the article, welcome to correct! Original is not easy, commercial reprint please contact the author to obtain authorization, non-commercial reprint please indicate the source.

【 Springboot source code analysis 】 Springboot source code analysis series of articles