preface

The last article “deep analysis of SpringBoot and handwritten a starter” after the fire, due to the reason of the space is not deep enough, I decided to add a more, two read down to believe that SpringBoot will certainly be able to understand.

Autoroing is things like transactions, Configuration file load and parse, AOP, caching, data sources, SpringMVC, etc. We used to have to configure XML or write @Configuration and a bunch of @ beans to load in our projects, all of that was written by hand, now we don’t have to, You just need to introduce the Spring-boot-Autoconfigure JAR package, which is the charm of automatic assembly. Things that used to be done manually are now done automatically.

This article covers the run method startup, SPI mechanism, and @enableAutoConfiguration.

Start the run method

We from SpringApplication. Run (SpringBootApp. Class, args); Method point to look at, the specific steps are written in the source code below.

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   DefaultBootstrapContext bootstrapContext = createBootstrapContext();
   ConfigurableApplicationContext context = null;
   configureHeadlessProperty();
   / / SPI for SpringApplicationRunListener instance
   SpringApplicationRunListeners listeners = getRunListeners(args);
   / / call SpringApplicationRunListener starting () method
   listeners.starting(bootstrapContext, this.mainApplicationClass);
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // Generate an Environment object
      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      configureIgnoreBeanInfo(environment);
      // Print the banner
      Banner printedBanner = printBanner(environment);
      / / create springboot AnnotationConfigServletWebServerApplicationContext context object
      context = createApplicationContext();
      context.setApplicationStartup(this.applicationStartup);
      // Initialize the context object
      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      // The core code to start the Spring container
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, listeners);
      throw new IllegalStateException(ex);
   }

   try {
     / / call SpringApplicationRunListener running () method
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, null);
      throw new IllegalStateException(ex);
   }
   return context;
}
Copy the code

The above source code does two main things

  • Complete the Spring container startup and instantiate the classes that need to be scanned
  • Start the Servlet container to finish starting the Servlet container

The core of the code above is refreshContext (context), the context object is AnnotationConfigServletWebServerApplicationContext, start a breakpoint

Eventually entered the AbstractApplicationContext refresh methods of a class, this is the core of the spring container startup method, so springboot underlying source or a spring.

Servlet container startupAs you go down, there’s an onRefresh methodPoints to a specific implementation class ServletWebServerApplicationContext, here is a createWebServer method

private void createWebServer(a) {
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
   if (webServer == null && servletContext == null) {
      StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
      / / here for TomcatServletWebServerFactory object
      ServletWebServerFactory factory = getWebServerFactory();
      createWebServer.tag("factory", factory.getClass().toString());
      // The Tomcat object is new and the Tomcat container is started
      this.webServer = factory.getWebServer(getSelfInitializer());
      createWebServer.end();
      getBeanFactory().registerSingleton("webServerGracefulShutdown".new WebServerGracefulShutdownLifecycle(this.webServer));
      getBeanFactory().registerSingleton("webServerStartStop".new WebServerStartStopLifecycle(this.this.webServer));
   }
   else if(servletContext ! =null) {
      try {
         getSelfInitializer().onStartup(servletContext);
      }
      catch (ServletException ex) {
         throw new ApplicationContextException("Cannot initialize servlet context", ex);
      }
   }
   initPropertySources();
}
Copy the code

Click on the factory. GetWebServer (getSelfInitializer ()); Enter the Tomcat implementation class

This section is about starting the Tomcat container and deploying the project into Tomcat.This is the end of the run method, combined with the source code to start the Spring container and Servlet container.

Mechanism of SPI

Introduction of SPI

SPI, or Service Provider Interface, is a Service discovery mechanism. It automatically loads the classes defined in the files by looking for them in the META-INF/services folder in the ClassPath path. This mechanism provides the possibility for many framework extensions, such as the USE of SPI mechanisms in Dubbo and JDBC. Let’s start with a very simple example of how it works. To put it simply, SPI is an extension mechanism. The core of SPI is to configure the service. On the premise that the core code is not changed, the service in the configuration file is loaded, and then the logic of which service is decided according to the parameters passed. This is open for extension and closed for modification.

The SPI SpringBoot

First we build a few classes in the package

Look at the code for each of them and define the interface

public interface Log {
    void debug(a);
}
Copy the code

Define Log4j implementation classes

public class Log4j implements Log {
    @Override
    public void debug(a) {
        System.out.println("-----Log4j"); }}Copy the code

Define the Logback implementation class

public class Logback implements Log {
    @Override
    public void debug(a) {
        System.out.println("-----Logback"); }}Copy the code

Define Slf4j implementation classes

public class Slf4j implements Log {
    @Override
    public void debug(a) {
        System.out.println("-----Slf4j"); }}Copy the code

Create a new Spring.factories under the Meta-INF file in the Resources file

Where key is a type (interface, annotation, abstract class, etc.) and value is an instance class

Write another test class

public class SpiTest {

    @Test
    public void test(a) {
        List<String> strings = SpringFactoriesLoader.loadFactoryNames(Log.class, ClassUtils.getDefaultClassLoader());
        for(String string : strings) { System.out.println(string); }}@Test
    public void test1(a) {
        List<Log> logs = SpringFactoriesLoader.loadFactories(Log.class, ClassUtils.getDefaultClassLoader());
        for(Log log : logs) { System.out.println(log); }}}Copy the code

Let’s do that. No problem. I got it all

Let’s analyze the SpringFactoriesLoader loadFactoryNames source, point in see factoryType. GetName () is the name of the access type, this is the key.

Click on it again

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
   // Take it from the cache according to the classLoader, and return it if it can get it
   Map<String, List<String>> result = cache.get(classLoader);
   if(result ! =null) {
      return result;
   }

   result = new HashMap<>();
   try {
      // The value of FACTORIES_RESOURCE_LOCATION is meta-INF /spring.factories
      // Get all the jar packages and all the Spring. factories in your project
      Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
      // Loop through all files
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         // Wrap as UrlResource objects
         UrlResource resource = new UrlResource(url);
         // The core code wraps the file as a Properties object
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         // Loop through the properties object
         for(Map.Entry<? ,? > entry : properties.entrySet()) {/ / get the key
            String factoryTypeName = ((String) entry.getKey()).trim();
            String[] factoryImplementationNames =
                  StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
            for (String factoryImplementationName : factoryImplementationNames) {
               // Add all instances of the key to the list container
               result.computeIfAbsent(factoryTypeName, key -> newArrayList<>()) .add(factoryImplementationName.trim()); }}}// Replace all lists with unmodifiable lists containing unique elements
      result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
            .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
      // create a cache
      cache.put(classLoader, result);
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
   return result;
}
Copy the code

We see Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); The FACTORIES_RESOURCE_LOCATION value is meta-INF /spring.factories, so when we did the above demo, the configuration was written in this file because that’s where the code was read.

And then we see here that Enumeration is urls, why is it urls, becauseThe “meta-INF /spring.factories” file in your project and the “meta-INF /spring.factories” file in the referencing JAR.

We loaded breakpoints, in the result in the discovery of 46 key value, but we only wrote in the project com. Jackxu. Zhengcheng. Spi. The Log, the three remaining is the key in other jar bag loading value. For example, we are familiar with the EnableAutoConfiguration, a total of 131, this is in the spring-boot-Autoconfigure package read out.

Finally SpringFactoriesLoader. LoadFactoryNames returns is the screenshot of com. Jackxu. Zhengcheng. Spi. The Log as a key to return to the three list, SpringFactoriesLoader. LoadFactories don’t analyze, just call the loadFactoryNames got the interface type corresponding all the name of the class, more than one step furtherReflection instantiationAnd nothing more.

@EnableAutoConfiguration

In the first article, @SpringBootApplication is introduced as a composite annotation, which includes @SpringBootConfiguration, @EnableAutoConfiguration and @ComponentScan. Here’s the @enableAutoConfiguration annotation.

The @enableAutoConfiguration annotation has another core annotation, @import, which was introduced in the first article and can introduce three types of objects:

  1. Normal beans or Configuration files with @Configuration
  2. Implement ImportSelector interface for dynamic injection
  3. Implement ImportBeanDefinitionRegistrar interface for dynamic injection

Here we use the second type, dynamic injection, and here’s how it’s written. Create three new classes.

This is the way to implement the DeferredImportSelector interface so that Spring can get the classes to instantiate.

/ * * *@author jack xu
 */
public class DeferredImportSelectorDemo implements DeferredImportSelector {

    // Return the class to be instantiated. This method spirng is not invoked directly, but is invoked for the following process method
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        System.out.println("==============DeferredImportSelectorDemo.selectImports===========");
        // If you need to instantiate a class, you need to return the class's fully qualified name
        return new String[]{DeferredBean.class.getName()};
    }

    // We need to return a class that implements the Group interface
    @Override
    public Class<? extends Group> getImportGroup() {
        return DeferredImportSelectorGroupDemo.class;
    }

    // Inner class that implements Group
    private static class DeferredImportSelectorGroupDemo implements Group {

        List<Entry> list = new ArrayList<>();


        // Spring will tune this method to collect classes that need to be instantiated
        @Override
        public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
            System.out.println("==============DeferredImportSelectorGroupDemo.process===========");
            String[] strings = selector.selectImports(metadata);
            for (String string : strings) {
                list.add(newEntry(metadata, string)); }}// Spring calls this method and returns it to the Spring container for instantiation
        @Override
        public Iterable<Entry> selectImports(a) {
            System.out.println("==============DeferredImportSelectorGroupDemo.selectImports===========");
            returnlist; }}}Copy the code

The bean that needs the instance

public class DeferredBean {

    @PostConstruct
    public void init(a) {
        System.out.println("DeferredBean, I loaded it with Import annotations."); }}Copy the code

Through Import, Spring scans and collects classes that need to be instantiated in Import annotations.

@Component
@Import(DeferredImportSelectorDemo.class)
public class ImportBean {}Copy the code

When I start executing, all the logs printed in the code are displayed, proving that Spring does follow this logic.

Look at the below AutoConfigurationImportSelector, indeed is also one of the routines, DeferredImportSelector interface is realized

Returns a class that implements the Group interface

Implement the Process method, selectImports method

And the process method, we guess will also be able to guess, its function is to collect need to spring container instantiation of the class, look at the point in getAutoConfigurationEntry method.

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if(! isEnabled(annotationMetadata)) {return EMPTY_ENTRY;
   }
   // Get the configuration property of @SpringBootApplication
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // Get the names of all the candidate classes
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   configurations = removeDuplicates(configurations);
   // Get the classes to exclude from the annotation configuration property
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   // Remove the class to be excluded from the candidate class
   configurations.removeAll(exclusions);
   //SPI extension to get filter instances for candidate classes
   configurations = getConfigurationClassFilter().filter(configurations);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   // Wrap all the candidate classes as AutoConfigurationEntry objects
   return new AutoConfigurationEntry(configurations, exclusions);
}
Copy the code

So how to collect is the way through the SPI said above, see points into getCandidateConfigurations method, SpringFactoriesLoader. LoadFactoryNames, obtained 131 types, and demonstrates the SPI number above.

You can collect all the @enableAutoConfiguration classes in the Spring. factories file and give them to Spring to instantiate. So as to achieve automatic assembly.

Afterword.

Finally, to demonstrate autowiring, write a Person class that needs to be injected

In the meta-inf/spring. Under the factories and org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration = com. Jackxu. Zhengcheng. Spi. Pe Rson this sentenceWhen you launch it, the Person class is injected

Deferredbeans are loaded in via the @import annotation,So when we want to load a class into the Spring container, there are two more ways. After reading this article, I believe we all understand the principle. Finally, thanks for watching, please give a like if you like!