Spring Boot@EnableAutoConfiguration parsing

When doing back-end development, the first contact is the basic spring. In order to provide bean s by referring to two-party packages, we need to add the corresponding package < context: component-scan base-package= "xxxx" /> or add the annotation @ComponentScan ({"xxx"}). At that time, I thought it was urgly, but I did not study whether there was a better way.

Until contact with Spring Boot, it was found that it could automatically introduce two-way packaged bean s. Nevertheless, I haven't looked at the implementation principle of this block. Not until recently was I asked about it in an interview. So look at the implementation logic.

 

Posture

Before explaining the principle, use the posture first.

Define a bean in project A.

package com.wangzhi;

import org.springframework.stereotype.Service;

@Service
public class Dog {
}

 

And create a file called spring.factories under resources/META-INF / of the project, which reads as follows

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wangzhi.Dog

 

Then refer to the jar package of project A in project B.

The project A code is as follows:

package com.wangzhi.springbootdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@EnableAutoConfiguration
public class SpringBootDemoApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args);
        System.out.println(context.getBean(com.wangzhi.Dog.class));
    }

}

 

Print results:

com.wangzhi.Dog@3148f668

 

Principle analysis

Overall, there are two parts: one is to collect all the classes of Enable AutoConfiguration related bean s in spring.factories, and the other is to register the obtained classes into spring containers.

Collecting bean definition classes

When the spring container starts, it calls AutoConfiguration Import Selector # getAutoConfiguration Entry

protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // EnableAutoConfiguration Attributes of annotations: exclude,excludeName etc.
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // Get all Configurations
    List<String> configurations = getCandidateConfigurations(annotationMetadata,
            attributes);
    // Duplicate removal
    configurations = removeDuplicates(configurations);
    // Delete exclude Classes specified in
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}
getCandidateConfigurations Calls to methods loadFactoryNames: 

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        // factoryClassName by org.springframework.boot.autoconfigure.EnableAutoConfiguration
        String factoryClassName = factoryClass.getName();
        // This method returns all spring.factories Documentation key by org.springframework.boot.autoconfigure.EnableAutoConfiguration Class path of
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }


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

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            // Find all"META-INF/spring.factories"
            Enumeration<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);
                // Read the contents of the file. properties Be similar to HashMap,Containing attributes key and value
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
                    // Property files can be used','Divide multiple value
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

 

Register to Container

In the above process, we get the class paths of all bean s specified in spring.factories, which are processed in the processGroupImports method. @import The same logic as the annotation imports it into the container.

public void processGroupImports() {
    for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
        // getImports That is, the encapsulation of all Classpaths obtained above
        grouping.getImports().forEach(entry -> {
            ConfigurationClass configurationClass = this.configurationClasses.get(
                    entry.getMetadata());
            try {
                // And processing@Import The same notes
                processImports(configurationClass, asSourceClass(configurationClass),
                        asSourceClasses(entry.getImportClassName()), false);
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                                configurationClass.getMetadata().getClassName() + "]", ex);
            }
        });
    }
}

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
    ...
    // Traversing the collected classpath
    for (SourceClass candidate : importCandidates) {
       ...
        //If candidate yes ImportSelector or ImportBeanDefinitionRegistrar Types will have different processing logic, not to mention here
         // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
        // Regard as @Configuration Handle            
        processConfigurationClass(candidate.asConfigClass(configClass));
   ...
}
            
    ...
}

 

As you can see, the bean class definition collected in the first step will eventually be registered in the container in the same way as the Configuration process.

End

@ The Enable AutoConfiguration annotation simplifies the cost of importing a two-party package bean. To provide a two-way package for other applications, you just need to define the exposed beans in the two-way package in spring.factories. For unnecessary beans, exclude can be done using the exclude attribute of @EnableAutoConfiguration.

 

I organize Java advanced materials for free, covering Java, Redis, MongoDB, MySQL, Zookeeper, Spring Cloud, Dubbo, high concurrent and distributed tutorials, a total of 30G, need to collect.
Portal: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q

Keywords: Java Spring Attribute Redis

Added by razvypp on Sat, 10 Aug 2019 11:00:34 +0300