First, summarize the Springboot automatic configuration process

Grasp three points:

1. Analyze the springApplication. run method in the main method, which creates the Spring container and refreshes it;

2. Analyze the @SpringApplication composite annotation, the @import annotation under the @enableAutoConfiguration;

3.SPI loading mechanism, load meta-INF /spring.fatories configuration file, load on demand;

When Springboot is started, it relies on the main method of the boot class, which executes the SpringApplication.run() method, which creates the spring container. And refresh the container. And when you refresh the container it’s going to parse the bootstrap class, and then it’s going to parse the @SpringBootApplication annotation on the bootstrap class, and that annotation is a compound annotation, and that annotation has an @enableAutoConfiguration annotation in it, and that annotation is going to enable automatic configuration, This annotation and @ Import annotations introduced a AutoConfigurationImportSelector this class, the class went to some core method, and then go to scan all our jar package under the spring under the meta-inf. Factories file, If you take the value of the full path of the EnableAutoConfiguration class from this configuration file, all of the following configurations are loaded, and all of these configurations are conditionally annotated, These conditional annotations are then loaded based on which JAR packages your current project depends on and whether the configuration that meets these conditional annotations is configured.

To put it in plain English, let me rephrase it:

SpringBoot calls the run() method on startup. The run() method refreshes the container and scans the meta-INF/Spring.Factories file in the package below the classpath where the auto-configuration classes are recorded. These auto-configuration classes are loaded into the container when the container is refreshed, and then are instantiated according to the condition annotations in the configuration classes. These conditions mainly determine whether the project has related JAR packages or imported related beans. So SpringBoot helps us with auto-assembly.

Second, source code analysis

1. Analyze the springApplication.run method:

        StopWatch stopWatch = newStopWatch(); Stopwatch.start (); ConfigurableApplicationContext context =null;
        Collection<SpringBootExceptionReporter> exceptionReporters = newArrayList(); # set the value of the system property Headlessthis.configureHeadlessProperty(); # to create start the listener SpringApplicationRunListeners listeners =this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try{# configure container arguments to encapsulate the commands used to start the container ApplicationArguments ApplicationArguments =newDefaultApplicationArguments(args); ConfigurableEnvironment = configures the external configuration to the environment container variable (including the PropertySource and profile to use, and notifies the listener container that the desired environment variable is configured)this.prepareEnvironment(listeners, applicationArguments); Skip the search for the BeanInfo class (usually used when the class is not defined for beans in the application in the first place)this.configureIgnoreBeanInfo(environment); # check whether the environment variable contains the banner identifier, if yes, print the banner printedBanner =this.printBanner(environment);
            # 1Create Application container context =this.createApplicationContext(); ExceptionReporters =this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, newClass[]{ConfigurableApplicationContext.class}, context); Create some operations for the containerthis.prepareContext(context, environment, listeners, applicationArguments, printedBanner); Refresh the Application containerthis.refreshContext(context);  
            this.afterRefresh(context, applicationArguments); Stopwatch.stop ();if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw newIllegalStateException(var9); }}Copy the code

SpringApplication. The run () method

  1. The first is to create the ApplicationContext container.
  2. The second is to refresh the ApplicationContext container.

When ApplicationContext is created, it is determined what type of ApplicationContext is created for the current SpringBoot application based on whether the ApplicationContextClass type is explicitly set by the user and the inferred results of the initialization phase.

protected ConfigurableApplicationContext createApplicationContext(a) { Class<? > contextClass =this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext"); }}catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3); }}return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }
Copy the code

With the ApplicationContext container created, we go back to the SpringApplication.run() method.

Let’s start initializing the prompts that various plug-ins give when an exception fails. You then perform some operations that prepare you to refresh the context. The prepareContext() method is also crucial, serving as a bridge between the two. Let’s take a look at what happens in the prepareContext() method.

  private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        this.postProcessApplicationContext(context);
        this.applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }

        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if(printedBanner ! =null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }

        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }

        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(newLazyInitializationBeanFactoryPostProcessor()); } Set<Object> sources = Set<Object> sources =this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty"); Load the startup classthis.load(context, sources.toArray(new Object[0])); # Listeners start loading ApplicationContext. ContextLoaded (context); }Copy the code

The main key is the getAllSoures() method, in which one source is obtained by launching the XXXApplication class. So you can load the startup class into the container by getting the startup class in the load() method.

ContextLoaded (context);

Load all listeners into the ApplicationContext container.

And finally, the second core refresh of the ApplicationContext container, which would have been useless without it, The last step, via the refreshContext(context) method of the SpringApplication, starts the annotation configuration on the class and refreshes it to the currently running container environment.

2. Start the annotation on the class

In the Run () method of the SpringApplication, we call our prepareContext() method, and in the prepareContext() method we call getAllSources(), and then we get the start class. The start class is then loaded via the Load () method of the SpringApplication and instantiated in the container when the container is refreshed.

When the ApplicationContext container is refreshed, the annotations on the startup class are parsed.

Starting the DemoApplication class has only one annotation @springBootApplication, so let’s look at this annotation:

You can see that this annotation is a composite annotation, with three key annotations that need to be explained.

@SpringBootConfiguration

The @springBootConfiguration annotation is an @Configuration annotation, indicating that the startup class is a Configuration class. Support for Spring to start as JavaConfig.

@ComponentScan

This is a Component scan, which by default scans the current package and spring annotations under its subpackages, such as @controller, @Service, @Component, and so on.

@EnableAutoConfiguration

@enableAutoConfiguration This annotation is also a compound annotation:

This annotation is a core annotation, springBoot’s main automatic configuration principle basically comes from @enableAutoConfiguration this annotation configuration, so we can find two annotations are more important by looking at the source of this annotation.

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)

Copy the code

@autoConfigurationPackage This annotation literally means AutoConfigurationPackage, so let’s click on it and see what it looks like inside.

It’s still a compound annotation, but ultimately depends on the @import annotation, which we’ll cover later, but which is just an annotation to introduce the functionality of the component into the Spring container.

Then we went on to look at AutoConfigurationPackages. The Registrar. The class inside the class code.

The picture is the AutoConfigurationPackages. The Registrar a key part of this class, to be honest, I am not see anything. The register() method is used to automatically register configurations in components, such as JPA’s @Entity annotation, which enables automatic scanning for such annotations.

** @Import(AutoConfigurationImportSelector.class)**

We then came back to see @ @ Import under EnableAutoConfiguration (AutoConfigurationImportSelector. Class) the function of this annotation. After entering into the AutoConfigurationImportSelector this class source code is as follows:

 public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); # getAutoConfigurationEntry. This method is very key AutoConfigurationImportSelector AutoConfigurationEntry AutoConfigurationEntry =this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }}Copy the code

Then we enter getAutoConfigurationEntry () method to look at:

 protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata); # getCandidateConfigurations is key to a List < String > configurations =this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return newAutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); }}Copy the code

We continue to enter getCandidateConfigurations () method:

  protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        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

Seems the most core method is SpringFactroiesLoader loadFactoryNames () method, we look into again:

 public static List<String> loadFactoryNames(Class<? > factoryType,@Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }
Copy the code

The package is so deep that it still has one layer, so keep going into the loadSpringFactories() method.

 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if(result ! =null) {
            return result;
        } else {
            try{ Enumeration<URL> urls = classLoader ! =null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                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[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); }}}Copy the code

LoadSpringFactories ()

First, the value of the constant FACTORIES_RESOURCE_LOCATION is:

"META-INF/spring.factories"
/** * The location to look for factories. * 

Can be present in multiple JAR files. */

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

So the first side core code means:

The meta-INF/Spring. factories files in all jar packages are scanned at startup. The second part of the code means to convert the scanned files into Properties objects, and the next two core pieces of code mean to put the loaded Properties objects into the cache.

Then getCandidateConfigurations () method, which is the only access to the key is EnableAutoConfiguration. The configuration of the class.

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        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

We see getCandidateConfigurations () method, through SpringFactoriesLoader loadFactoryNames () get to 118 configuration (TBC).

So let’s configure the first class:

Org. Springframework. Boot. Autoconfigure. Admin. SpringApplicationAdminJmxAutoConfiguration look, if these classes are implemented.

Open the org. Springframework. Boot. Autoconfigure. Admin. SpringApplicationAdminJmxAutoConfiguration source code:

We can see that this class has three annotations @Configuration, @AutoConfigureAfter, @conditionAlonProperty, and it’s also a Configuration class because of the @Configuration annotation. Then the second annotation JmxAutoConfiguration the parameters in the class. After class to is this: is the existence of @ ConditionalOnProperty annotations. The key, it seems, is the @conditionalonproperty annotation.

This annotation is a conditional judgment annotation. The parameter behind this conditional annotation means that the current Bean will only be loaded and instantiated if there is a system property prefixed with Spring.application.admin, the property name is enabled, and the value is true. Conditional annotations, which appear in spring4.0, can greatly increase the flexibility and extensibility of the framework, ensuring that many components can be configured later, and that readers of the source code can understand the circumstances under which the current Bean should be instantiated.