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.

Autowaging is one of the cores of SpringBoot, and this article will explore how SpringBoot loads autowaging classes.

In the @SpringBootApplication annotation article, we mentioned that springBoot handles auto-assembly with @enableAutoConfiguration as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// Automatic package assembly
@AutoConfigurationPackage
// The introduced autowiring class
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    /** * you can define classes */ that exclude autowiringClass<? >[] exclude()default {};

    /** * You can define the name of the class that excludes autowiring */
    String[] excludeName() default {};

}
Copy the code

The above code consists of three parts:

  1. @AutoConfigurationPackage: specifies the package for automatic assembly;
  2. @Import(AutoConfigurationImportSelector.class): Introduces automatic assembly processing classesAutoConfigurationImportSelectorThis class is the key to autowiring;
  3. @EnableAutoConfigurationProperties:@EnableAutoConfigurationTwo properties are provided:excludewithexcludeName, can be used to exclude classes that do not require autowiring.

This paper to analyze AutoConfigurationImportSelector class.

1. AutoConfigurationImportSelector.AutoConfigurationGroup

The analysis of the AutoConfigurationImportSelector implements DeferredImportSelector about DeferredImportSelector, You can refer to the processing of ConfigurationClassPostProcessor @ Import annotations, here we directly give the conclusion:

  • DeferredImportSelector is a subinterface of ImportSelector with an internal interface Group that defines two methods:

    public interface DeferredImportSelector extends ImportSelector {...interface Group {
    
            /** * handle the import operation */
            void process(AnnotationMetadata metadata, DeferredImportSelector selector);
    
            /** * returns the imported class */
            Iterable<Entry> selectImports(a)}}Copy the code

    In dealing with the import DeferredImportSelector class, DeferredImportSelector Group# process method will first call, Then call DeferredImportSelector. Group# selectImports return to import classes;

  • DeferredImportSelector can specify a group of imported classes. When processing, the imported classes can be processed by group.

  • DeferredImportSelector handles imported classes by grouping them into a map, After processing the other Configuration classes (Spring’s Configuration classes are @Component, @ComponentScan, @import, @Configuration, @Bean tagged classes), we can process the imported classes in the group. That is, DeferredImportSelector imports classes that will be registered with the beanFactory after other classes are registered with it.

Let’s take a look at AutoConfigurationImportSelector code:

// Implements DeferredImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector.BeanClassLoaderAware.ResourceLoaderAware.BeanFactoryAware.EnvironmentAware.Ordered {.../ * * * here implements DeferredImportSelector. Group * /
    private static class AutoConfigurationGroup implements DeferredImportSelector.Group.BeanClassLoaderAware.BeanFactoryAware.ResourceLoaderAware {

        /** * Save the imported class */
        private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();

        /** * handles the import class */
        @Override
        public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                    () -> String.format("Only %s implementations are supported, got %s",
                            AutoConfigurationImportSelector.class.getSimpleName(),
                            deferredImportSelector.getClass().getName()));
            / / 1. Call AutoConfigurationImportSelector# getAutoConfigurationEntry (...). Method,
            // The autoloader class is loaded in this method
            AutoConfigurationEntry autoConfigurationEntry = 
                ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
            // 2. Save the obtained autoConfigurationEntry
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata); }}/** * returns the imported class */
        @Override
        public Iterable<Entry> selectImports(a) {
            if (this.autoConfigurationEntries.isEmpty()) {
                return Collections.emptyList();
            }
            // 3. Get the filter class
            Set<String> allExclusions = this.autoConfigurationEntries.stream()
                    .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream)
                    .collect(Collectors.toSet());
            // 4. Convert autoConfigurationEntries to LinkedHashSet
            Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
                    .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
                    .collect(Collectors.toCollection(LinkedHashSet::new));
            // 5. Remove classes that need to be filtered
            processedConfigurations.removeAll(allExclusions);
            // 6
            return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata())
                    .stream().map((importClassName) -> new Entry(
                        this.entries.get(importClassName), importClassName)) .collect(Collectors.toList()); }... }}Copy the code

. Here we will DeferredImportSelector Group# process with DeferredImportSelector. Group# selectImports combined two methods, the steps summarized below:

  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.

Let’s take a look at these key steps.

SelectImports () {selectImports(); selectImports(); selectImports(); selectImports(); In DeferredImportSelector, we also override this method:

All this method does is load the autorungclass and return the final imported class, but note that SpringBoot’s autorungclass is not handled here. At this point, you can make a breakpoint inside the method and see that the method does not run!

One final statement: Springboot automatically import the classes not in AutoConfigurationImportSelector# selectImports methods in the treatment of, But in AutoConfigurationImportSelector. AutoConfigurationGroup# selectImports processing method.

2. Obtain assembly class:AutoConfigurationImportSelector#getAutoConfigurationEntry

The loading code for the auto-configuration class is:

AutoConfigurationEntry autoConfigurationEntry = 
    ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
Copy the code

This code is used to load automatic assembling of the class, we directly into AutoConfigurationImportSelector# getAutoConfigurationEntry method:

protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    // Check whether auto assembly is enabled again
    if(! isEnabled(annotationMetadata)) {return EMPTY_ENTRY;
    }
    // Get the attributes of the annotation
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 1. Load the candidate auto-configuration class
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // select * from set to list
    configurations = removeDuplicates(configurations);
    // 3. Exclude and excludeName of @enableAutoConfiguration
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    // 4. Filter classes that do not require autowiring
    configurations = filter(configurations, autoConfigurationMetadata);
    / / 5. Trigger AutoConfigurationImportEvent events
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // 6. The final return value
    return new AutoConfigurationEntry(configurations, exclusions);
}
Copy the code

This method is very important and contains all the operations to get the autowire class as follows:

  1. Load the candidate autowler classes. Springboot autowler classes are in meta-INF/Spring.Factories on your classpath. The key for org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration, this behind us again detailed analysis;

  2. In this case, the duplicate classes will be removed. The way to remove the duplicate classes is also very simple. Springboot just converts to Set first, and then to List.

  3. Exclusion of excluded classes. As mentioned earlier, @enableAutoConfiguration can exclude and excludeName classes that need to be excluded. This step handles these two properties.

  4. Filter classes that do not need automatic assembly, according to my debugging, found that the filtering is not completed:

    Before filtering, it was 124:

    After filtering, there are still 124:

  5. Trigger AutoConfigurationImportEvent events;

  6. Wrap the exclusion class from Step 3 and the autowore class from Step 4 and return it as an AutoConfigurationEntry.

Notice the last line of code:

// 6. The final return value
return new AutoConfigurationEntry(configurations, exclusions);
Copy the code

Configurations and Exclusions are passed into the AutoConfigurationEntry constructor. Let’s look at AutoConfigurationEntry:

protected static class AutoConfigurationEntry {
    // Automatic assembly class
    private final List<String> configurations;

    // The autowiring classes that need to be excluded
    private final Set<String> exclusions;

    /** * constructor, which assigns the value */
    AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {
        this.configurations = new ArrayList<>(configurations);
        this.exclusions = newHashSet<>(exclusions); }... }Copy the code

As you can see, the final AutoConfigurationEntry returned contains two things:

  • configurations: Autowiring classes have been removed to exclude classes
  • exclusionsThrough:@EnableAutoConfigurationSpecifies the classes to exclude

Now that the entire autowiring class is acquired, let’s look at the process of loading the candidate autowiring class.

3. Load the candidate autowiring classes

Automatic assembly of class loading in AutoConfigurationImportSelector# getCandidateConfigurations, the code is as follows:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    / / call the method that is spring provides: SpringFactoriesLoader. LoadFactoryNames (...).
    / / getSpringFactoriesLoaderFactoryClass EnableAutoConfiguration () returns
    List<String> configurations = SpringFactoriesLoader
            .loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    Assert.notEmpty(configurations, "...");
    return configurations;
}

protectedClass<? > getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;
}
Copy the code

Proceed to SpringFactoriesLoader#loadFactoryNames:


public final class SpringFactoriesLoader {

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    public static List<String> loadFactoryNames(Class<? > factoryType,@Nullable ClassLoader classLoader) {
        / / get factoryTypeName is org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

    /** * The META-INF/spring.factories property */ is loaded here
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if(result ! =null) {
            return result;
        }

        try {
            // Load the content of meta-INF /spring.factoriesEnumeration<URL> urls = (classLoader ! =null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                // Convert the content of meta-INF/spring.Factories to a Properties object
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for(Map.Entry<? ,? > entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim();// StringUtils.commaDelimitedListToStringArray(...) Commas split into arrays
                    for (String factoryImplementationName : 
                                StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryTypeName, factoryImplementationName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex); }}... }Copy the code

As you can see, the meta-INF/Spring. factories file is loaded from your classpath. Note that there can be multiple of these files in different JAR packages.

The meta-INF /spring.factories (” spring-boot-autoconfigure “) are in the spring-boot-autoconfigure module.

Let’s take a look at spring.factories:

This file defines a number of configuration classes, stored as key-values, separated by commas (,), Mentioned above automatic assembly class key is org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration, the corresponding value very much, does not show here.

After this step, the autowiring classes are registered with the Spring container. Note: when loading into the spring container or BeanDefinition, to be spring bean, also have to pass the test of ConditionalOnBean, ConditionalOnClass annotations, these reanalysis behind us.

4. Processing after obtaining the automatic assembly class

Let’s go back to AutoConfigurationImportSelector AutoConfigurationGroup, in section 1 we summarize process is as follows:

  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.

Sections 2 and 3 above examined the loading process of auto-loading classes, so let’s take a look at the next steps.

The next steps are easy to follow in the code, so let’s go through them one by one.

  • Step 2, save the resulting autowiring class, which simply calls List#add(…) Method to save the resulting autoConfigurationEntry to autoConfigurationEntries, which are members of the AutoConfigurationGroup. In AutoConfigurationImportSelector. AutoConfigurationGroup# selectImports method will be used;

  • Step 3: Obtain all filter classes by iterating through autoConfigurationEntries and using the autoConfigurationEntry#getExclusions method. The autoConfigurationEntry contains only two member variables: Configurations (the auto-assembly class after excluding the excluded classes) and Exclusions (the excluded classes specified via @enableAutoConfiguration).

  • Step 4, convert the List to LinkedHashSet, without analyzing;

  • Step 5: Remove the excluded classes from all the autowiring classes again. The excluded objects are all the excluded classes. In this case, there should be multiple @enableAutoConfiguration in the same project. For example, the first @enableAutoConfiguration annotation excludes A and B classes, and the second @enableAutoConfiguration annotation excludes C and D classes, which ultimately excludes A, B, C, and D classes.

  • Step 6, the main operation of this step is sorting. This order determines the order in which the autologister classes are registered with the beanFactory. AutoConfigureOrder, @AutoConfigureAfter, and @AutoConfigureBefore are handled here. For this, see the SpringBoot autowiring sequence.

After these steps, the acquisition of autowiring is complete.

5. Customize autowiring classes

Now that we know how to load the autowiring class, we can also customize an autowiring class.

  1. Prepare an autowiring class
@Configuration
public class MyAutoConfiguration {

    @Bean
    public Object object(a) {
        System.out.println("create object");
        return newObject(); }}Copy the code

This class is simply a class marked @Configuration that uses the @Bean annotation to create a Bean, printing “Create Object” during Bean creation.

  1. To prepareMETA-INF/spring.factories

As follows:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.learn.autoconfigure.demo01.configure.MyAutoConfiguration
Copy the code
  1. The main class
@SpringBootApplication
public class AutoconfigureDemo01Application {

    public static void main(String[] args) { SpringApplication.run(AutoconfigureDemo01Application.class, args); }}Copy the code

The running results are as follows:

As you can see, the Create Object prints successfully.

Is this bean created through a package scan or is it auto-assembled and imported? Let’s take a look at the autowiring classes through debugging:

As you can see, MyAutoConfiguration is in the list of autowiring classes.

Note that MyAutoConfiguration is annotated with @Configuration. Is it scanned by the SPing container or by autowiring?

In the @SpringBootApplication annotation, we mentioned that @ComponentScan in the SpringBootApplication annotation specifies a filter: AutoConfigurationExcludeFilter, this filter will filter automatic assembly, here we look at what is the beanFactory beanName so far:

As you can see, there is no MyAutoConfiguration, so it has not been scanned into the beanFactory at this point.

Of course, we can also remove the @Configuration annotation from MyAutoConfiguration to avoid this confusion.

6. Summary

This article embarks from the @ EnableAutoConfiguration annotation, this paper analyzes the automatic assembling of the class loading process, loading process in AutoConfigurationImportSelector# getAutoConfigurationEntry method, Ultimate load is the meta-inf/spring. The key factories file is org springframework. Boot. Autoconfigure. EnableAutoConfiguration class.

Get automatic assembly after class, spring will be registered with the container, they still a BeanDefinition, at this time to be spring bean, also have to pass the test of ConditionalOnBean, ConditionalOnClass annotations, We’ll talk about that later.


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