Describes the SpringBoot automatic configuration principle

This article has been synchronized to the GitHub open source project: The Way of the Supergods in Java

The point of SpringBoot is that convention is more important than configuration. In the early stages of development, we don’t need to do much configuration. SpirngBoot has already done most of the auto-configuration for us, such as arbitrating dependencies, automatically introducing needed dependencies, auto-configuration, etc. Allows us to focus more on business logic, so how does it automate configuration?

First we can see that there is an annotation @SpringBootApplication on the SpringBoot boot class.

Next, let’s analyze this annotation. Click on it and find that it is mainly composed of the following annotations.

@SpringBootConfiguration // indicates that this is a configuration class
@EnableAutoConfiguration
@ComponentScan // Packet scanning rules
Copy the code

Let’s go through them one by one.

@SpringBootConfiguration

If we click on it, we see that it’s a Configuration

@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor( annotation = Configuration.class )
    boolean proxyBeanMethods(a) default true;
}
Copy the code

We’ve already seen this annotation in Spring, which represents the current configuration class, so the @SpringBootConfiguration annotation in SpringBootApplicaton serves to indicate that the startup class is a configuration class.

@ComponentScan

As we know from previous Spring, this annotation means that the IoC container can scan packages in the way specified in this annotation when registering without too much hassle.

@EnableAutoConfiguration

@AutoConfigurationPackage // Batch register by the package name of the main program
@Import(AutoConfigurationImportSelector.class) //
public @interface EnableAutoConfiguration {

   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<? >[] exclude()default {};

   String[] excludeName() default {};

}
Copy the code

This annotation consists mainly of two annotations. We’re going to look at it one by one

  • @AutoConfigurationPackage: Automatic configuration package

    @Import(AutoConfigurationPackages.Registrar.class) // Batch register by the package name of the main program
    public @interface AutoConfigurationPackage {
     String[] basePackages() default{}; Class<? >[] basePackageClasses()default {};
    
    }
    Copy the code

    We found that the annotations by @ Import (AutoConfigurationPackages. The Registrar. The class) to Import a component in the IoC container AutoConfigurationPackages. The Registrar

    When we click in, we find that this is a class made up of methods, as shown below

    static class Registrar implements ImportBeanDefinitionRegistrar.DeterminableImports {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
           register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }
    
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
           return Collections.singleton(newPackageImports(metadata)); }}Copy the code

    We type a breakpoint here and Debug it for analysis.

    We find that this method imports a series of components into the container

    Debug shows that the metadata parameter represents the original SpringBootApplication startup class

    In the code, we see that it new a PackageImports object, passes in the launch class, then calls the getPackageNames() method and gets a package name, debug finds, The package name returned is the package name from our own project, cn.shaoxiongdu, and we find that it encapsulates the package name as an argument in the String array, calling the register method.

    So the register method is a batch registration of components by the package name, which is the package in which the main program class is located. This is why the default package scanning rule is the package in which the main program class resides.

    So the first part of EnableAutoConfiguration, the AutoConfigurationPackage, is used to batch register the main program by the package name. Let’s look at the second annotation.

  • @Import(AutoConfigurationImportSelector.class)

    We found that this was a class, clicked in, and found the main methods as follows

    @Override
     public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if(! isEnabled(annotationMetadata)) {return NO_IMPORTS;
        }
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
     }
    Copy the code

    This method returns an array of bean names that we need to register with the container. So here’s our point.

    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); // We need an array of bean names to register with the container
    Copy the code

    Click on this method, and let’s continue analyzing this method.

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if(! isEnabled(annotationMetadata)) {return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // Get all candidate components that need to be registered
        configurations = removeDuplicates(configurations); // Remove duplicate components
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
     }
    Copy the code

    If you want to update the List of beans, you need to update the List of beans. If you want to update the List of beans, you need to update the List of beans. If you want to update the List of beans, you need to update the List of beans.

    So we only need to analysis line 6 this method getCandidateConfigurations (annotationMetadata, attributes);

    Is that it returns a character array of the names of the beans we need to register with the container by default.

    Let’s Debug again, enter the method

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
              getBeanClassLoader()); // Get the set of components to register
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
              + "are using a custom packaging, make sure that file is correct.");
        return configurations;
     }
    Copy the code

    Through our analysis, we found that the main process is in line 2, loading the set of containers that need to be registered through the factory pattern.

    Continue to Debug into this method

    public static List<String> loadFactoryNames(Class<? > factoryType,@Nullable ClassLoader classLoader) {
            ClassLoader classLoaderToUse = classLoader;
            if (classLoader == null) {
                classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
            }
    
            String factoryTypeName = factoryType.getName();
            return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); // Return the collection of components to be registered
        }
    Copy the code

    In the last line, the loadSpringFactories method returns the corresponding collection.

    Continue to Debug into this method

    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
            Map<String, List<String>> result = (Map)cache.get(classLoader); 
            if(result ! =null) {
                return result;
            } else {
                HashMap result = new HashMap(); 
    
                try {
                    Enumeration urls = classLoader.getResources("META-INF/spring.factories");
    
                    while(urls.hasMoreElements()) {
                        URL url = (URL)urls.nextElement();
                        UrlResource resource = new UrlResource(url);
                        Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                        Iterator var6 = properties.entrySet().iterator();
    
                        while(var6.hasNext()) { Entry<? ,? > entry = (Entry)var6.next(); String factoryTypeName = ((String)entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); String[] var10 = factoryImplementationNames;int var11 = factoryImplementationNames.length;
    
                            for(int var12 = 0; var12 < var11; ++var12) {
                                String factoryImplementationName = var10[var12];
                                ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                    return new ArrayList();
                                })).add(factoryImplementationName.trim());
                            }
                        }
                    }
    
                    result.replaceAll((factoryType, implementations) -> {
                        return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                    });
                    cache.put(classLoader, result);
                    return result;
                } catch (IOException var14) {
                    throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14); }}}Copy the code

    This method returns the set of components to be registered. We can analyze this method.

    First, debug finds that the code has come to line 6, creating a HashMap. Then inside the try, we find that it loads a resource file, meta-inf /spring.factories, and cycles through all of its dependencies with this file. By looking at it, we find that most of the dependencies have this file, and a few do not.

    We open the spring-boot-autoConfiguration dependency and open his spring.factories

    There is a key for org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration item

    Values are called XXXConfiguration. An XXXConfiguration corresponds to a dependent autoconfiguration class

    In other words, we write all the configuration classes that spring-boot loads into the container when it starts in the spring.factories file that spring-boot-autoConfiguration depends on, and we run the following main method

    public static void main(String[] args) {
    
            // Return the IoC container
            ConfigurableApplicationContext run = SpringApplication.run(Springboot01HelloApplication.class, args);
            
            int beanDefinitionCount = run.getBeanDefinitionCount();
            System.out.println("beanDefinitionCount = " + beanDefinitionCount);
    
        }
    Copy the code

    Only 143 were found.

    That is, not all components are registered with the container. By looking at some configuration classes in this dependency, we can see that

    Most classes have an @conditional annotation, which means that Conditional registration in the container does not necessarily get loaded. Only if the condition is met will it be loaded.

conclusion

  • For our custom components:

    • through@AutoConfigurationPackageannotations
    • The final callregister(registry, new PackageImports(metadata).getPackageNames();Method to get the components under the startup class’s package for circular registration.
  • For other components:

    • SpringBoot loads all the autoconfiguration classes xxxxxAutoConfiguration first
    • Each autoconfiguration class takes effect conditionally and is bound by default to the value specified in the configuration file. XxxxProperties inside take. XxxProperties is bound to the configuration file
    • If the condition is met, it is registered in the container
    • Customized configuration
      • Users replace the underlying component directly with their @bean
      • The user sees what value the component gets from the configuration file and modifies it.

XxxxxAutoConfiguration –> component –> xxxxProperties take the value —-> application.properties

This is the underlying principle of SpringBoot’s automatic configuration function. Welcome to point out the shortcomings.

This article has been synchronized to the GitHub open source project: The Path of The Java Supergods for more Java knowledge, please visit!

Copy the code