Spring boot principle: the starting principle of source code analysis

Posted by aseaofflames on Mon, 22 Jun 2020 10:48:12 +0200

catalog

1, Introduction to spring boot analysis

2, Spring boot annotations to be covered

1.@SpringBootApplication

2.@SpringBootConfiguration

3.@EnableAutoConfiguration

4.@AutoConfigurationPackage

5. Some annotations in the xxxautoconfiguration class

3, Spring application initialization process of source code analysis

4, Source code analysis SpringApplication.run Method flow

1.StopWatch effect

2.getRunListeners method

2.1 S pringFactoriesLoader.loadFactoryNames () method

3.SpringApplicationRunListeners class

4.prepareEnvironment method

5.configureIgnoreBeanInfo method

6.printBanner method

7.createApplicationContext method

8.prepareContext method

9.refreshContext method

9.1 automatic integration principle of Tomcat container

9.2 automatic integration principle of spring MVC

9.3 automatic integration of mybatis

10.afterRefresh method

11.callRunners method

Before reading this article, you'd better understand the general composition of the spring boot framework, the portal: (1) Spring boot principle source code analysis: spring boot framework composition.

1, Introduction to spring boot analysis

As we all know, springboot is so popular because of its two major features: out of the box and convention over configuration.

  1. Out of the box: out of the box refers to adding related dependency packages in the pom file of the MAVEN project during the development process, and then using corresponding annotations to replace the cumbersome XML configuration file to manage the object life cycle. This feature is reflected in the convenience of creating springboot applications. Just introduce the springboot starter package, you can immediately use the framework for development. If you want to support web or persistence, you can import their starter package directly.
  2. Convention over configuration: Convention is better than configuration. Convention over configuration is a software design paradigm in which the target structure is configured by spring boot itself and the developer adds information to the structure. Reduce the configuration of various collections of spring and other frameworks to add corresponding properties in the yml file or the properties file. Users can directly configure several framework properties after referencing the package to complete framework integration and customization.

The above two points can be said to be the soul of springboot. There are two sides to everything. Springboot greatly speeds up the coding speed of developers. However, if the framework is not configured properly or in similar situations, it is difficult for developers to find out the problem. Because of its convenience, most of the developers have details about the integration and the framework Some of the characteristics and deep-seated principles of will be ignored, making developers more and more lazy, which will have a certain negative impact on future career competition and career development.

Therefore, this article simply analyzes the start-up principle of springboot. The premise of reading this article is to understand the Spring start-up principle, Spring MVC start-up integration principle, Mybatis start-up principle and Mybatis and Spring integration principle. Otherwise, the specific details cannot be understood during the analysis.

2, Spring boot annotations to be covered

1.@SpringBootApplication

The annotation source code is as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, 
        classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, 
        classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

   @AliasFor(annotation = EnableAutoConfiguration.class)
   Class<?>[] exclude() default {};

   @AliasFor(annotation = EnableAutoConfiguration.class)
   String[] excludeName() default {};

   @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
   String[] scanBasePackages() default {};

   @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
   Class<?>[] scanBasePackageClasses() default {};

}

This annotation is the startup class of Springboot. The key annotations are @ SpringBootConfiguration and @ EnableAutoConfiguration. The first annotation indicates that the annotated class is @ Configuration configuration Configuration class, and the second annotation indicates that the automatic Configuration is started.

2.@SpringBootConfiguration

The annotation source code is as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

3.@EnableAutoConfiguration

The annotation source code is as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

   Class<?>[] exclude() default {};

   String[] excludeName() default {};

}

There is an @ Import annotation in the annotation, including the AutoConfigurationImportSelector class property. The implementation class of this class is an important part of springboot startup, which will put meta-inf in the package/ spring.factories Read the file, and then instantiate the data configuration one by one for later use.

4.@AutoConfigurationPackage

The annotation source code is as follows:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

The function of this annotation is to take the class annotated by this annotation as the default path if the basic path is not configured. The effect is equivalent to @ springbootapplication (scanbasepackages =“ XXX.XXX "), XXX.XXX Is the location of the annotation class.

5. Some annotations in the xxxautoconfiguration class

For example, the auto configuration class of mybatis is MybatisAutoConfiguration. In this class, there are generally the following notes, which work as follows:

  • @ConditionalOnClass: takes effect when the class exists in the classpath;
  • @Works when the class does not exist in the ConditionalOnMissingClass: classpath;
  • @ConditionalOnBean: takes effect when the type Bean exists in the DI container;
  • @ConditionalOnMissingBean: takes effect when the type Bean does not exist in the DI container;
  • @ConditionalOnWebApplication: works in the Web application environment;
  • @ConditionalOnSingleCandidate: when there is only one Bean of this type in the DI container or only one Bean of @ Primary, it will take effect;
  • @ConditionalOnExpression: when the result of the SpEL expression is true;
  • @ConditionalOnProperty: effective when the parameter settings or values are consistent;
  • @ConditionalOnResource: takes effect when the specified file exists;
  • @ConditionalOnJndi: takes effect when the specified JNDI exists;
  • @ConditionalOnJava: takes effect when the specified Java version exists;
  • @Conditional on not Web application: works in a non Web application environment.

The commonly used annotations are generally the first five, and the others are only used in non mainstream frameworks.

3, Spring application initialization process of source code analysis

Its source code is as follows:

public class SpringApplication {
   public static final String DEFAULT_CONTEXT_CLASS = "org." +
           "springframework.context.annotation." +
           "AnnotationConfigApplicationContext";
   public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org." +
           "springframework.boot.web.servlet.context." +
           "AnnotationConfigServletWebServerApplicationContext";
   public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org." +
           "springframework.boot.web.reactive.context." +
           "AnnotationConfigReactiveWebServerApplicationContext";
   public static final String BANNER_LOCATION_PROPERTY_VALUE = 
           SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION;
   public static final String BANNER_LOCATION_PROPERTY = 
           SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY;
   private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = 
           "java.awt.headless";
   private static final Log logger = 
           LogFactory.getLog(SpringApplication.class);
   private Set<Class<?>> primarySources;
   private Set<String> sources = new LinkedHashSet<>();
   private Class<?> mainApplicationClass;
   // Use console mode output by default
   private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
   // Objects that show the Spring icon at startup
   private Banner banner;
   private ResourceLoader resourceLoader;
   private BeanNameGenerator beanNameGenerator;
   // Environment objects, saving runtime properties
   private ConfigurableEnvironment environment;
   // ApplicationContext class type
   private Class<? extends ConfigurableApplicationContext> 
           applicationContextClass;
   // Record the Springboot startup type. The default is SERVLET
   private WebApplicationType webApplicationType;
   // Collection of initialization classes before startup context
   private List<ApplicationContextInitializer<?>> initializers;
   // Listening class collection
   private List<ApplicationListener<?>> listeners;
   public SpringApplication(Class<?>... primarySources) {
       this(null, primarySources);
    }
    public SpringApplication(ResourceLoader resourceLoader, 
            Class<?>... primarySources) {
       this.resourceLoader = resourceLoader;
       Assert.notNull(primarySources, "PrimarySources must not be null");
       this.primarySources = new LinkedHashSet<>(Arrays
               .asList(primarySources));
       this.webApplicationType = WebApplicationType.deduceFromClasspath();
       setInitializers((Collection) getSpringFactoriesInstances(
               ApplicationContextInitializer.class));
       setListeners((Collection) getSpringFactoriesInstances(
               ApplicationListener.class));
       this.mainApplicationClass = deduceMainApplicationClass();
    }
    // Instantiate after getting the class path using SpringFactoriesLoader
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
       return getSpringFactoriesInstances(type, new Class<?>[] {});
    }
    // Instantiate after getting the class path using SpringFactoriesLoader
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, 
            Class<?>[] parameterTypes, Object... args) {
       ClassLoader classLoader = getClassLoader();
       Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader
               .loadFactoryNames(type, classLoader));
       List<T> instances = createSpringFactoriesInstances(type, 
               parameterTypes, classLoader, args, names);
       AnnotationAwareOrderComparator.sort(instances);
       return instances;
    }
    // Instantiate the obtained Class type
    private <T> List<T> createSpringFactoriesInstances(Class<T> type, 
            Class<?>[] parameterTypes,
      ClassLoader classLoader, Object[] args, Set<String> names) {
       List<T> instances = new ArrayList<>(names.size());
       for (String name : names) {
          try {
             Class<?> instanceClass = ClassUtils.forName(name, classLoader);
             Assert.isAssignable(type, instanceClass);
             Constructor<?> constructor = instanceClass
                     .getDeclaredConstructor(parameterTypes);
             T instance = (T) BeanUtils.instantiateClass(constructor, args);
             instances.add(instance);
          }
          catch (Throwable ex) {
             throw new IllegalArgumentException("Cannot instantiate " + 
                     type + " : " + name, ex);
          }
       }
       return instances;
    }
    // run method actually called
    public static ConfigurableApplicationContext run(
            Class<?> primarySource, String... args) {
       return run(new Class<?>[] { primarySource }, args);
    }
    // run method of external call
    public static ConfigurableApplicationContext run(
            Class<?>[] primarySources, String[] args) {
       return new SpringApplication(primarySources).run(args);
    }
}

Start with the main function and start with the SpringApplicationo.run() start analysis, the general process is as follows:

  1. Enter the run() method;
  2. Enter the SpringApplication(arg1,arg2) constructor;
  3. Assign resourceLoader object and primarySources to member variables;
  4. Call getSpringFactoriesInstances() method from spring.factories Obtain the implementation subclasses of ApplicationContextInitializer and ApplicationListener, and add them to the member variables initializers and listeners respectively;
  5. The deduceMainApplicationClass() method is assigned to the mainApplicationClass member variable, which is usually called in the invocation chain. SpringApplicationo.run The main method of ().

 

4, Source code analysis SpringApplication.run Method flow

The source code of the run method of its class is as follows:

public class SpringApplication {
    public ConfigurableApplicationContext run(String... args) {
       StopWatch stopWatch = new StopWatch();
       stopWatch.start();
       ConfigurableApplicationContext context = null;
       Collection<SpringBootExceptionReporter> exceptionReporters = 
               new ArrayList<>();
       configureHeadlessProperty();
       SpringApplicationRunListeners listeners = getRunListeners(args);
       listeners.starting();
       try {
          ApplicationArguments applicationArguments = 
                  new DefaultApplicationArguments(args);
          ConfigurableEnvironment environment = 
                  prepareEnvironment(listeners, applicationArguments);
          configureIgnoreBeanInfo(environment);
          Banner printedBanner = printBanner(environment);
          context = createApplicationContext();
          exceptionReporters = getSpringFactoriesInstances(
                      SpringBootExceptionReporter.class,
                      new Class[] { ConfigurableApplicationContext.class },
                      context);
          prepareContext(context, environment, listeners, 
                  applicationArguments, printedBanner);
          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, exceptionReporters, listeners);
          throw new IllegalStateException(ex);
       }
       try {
          listeners.running(context);
       }
       catch (Throwable ex) {
          handleRunFailure(context, ex, exceptionReporters, null);
          throw new IllegalStateException(ex);
       }
       return context;
    }
}

Next, it analyzes its specific process and role:

  1. Declare the stopwatch object stopwatch, call its start method, and record the time from the start of the run method;
  2. Call the configureHeadlessProperty method, which is the headlessProperty property;
  3. Call getRunListeners method according to the passed args to get SpringApplicationRunListeners object listeners;
  4. call listeners.starting();
  5. Initialize the DefaultApplicationArguments object applicationArguments according to args, and call the parse method of SimpleCommandLineArgsParser in its constructor. The method is to parse instructions such as -- XX=XX into properties in the CommandLineArgs object;
  6. According to applicationArguments and listener, the prepareEnvironment() method is called to initialize the Environment, in which the yml, xml and properties files are read, but the data is not parsed;
  7. Call the printBanner() method to get the banner object from the environment object;
  8. Call the createApplicationContext method to create the corresponding ApplicationContext context. Note that the context object will be a subclass of ConfigurableApplicationContext;
  9. Call the getSpringFactoriesInstances method to get all the subclasses of SpringBootExceptionReporter and put them into the exceptionReporters array, so that when exceptions are thrown, a class can handle these exceptions;
  10. Call the prepareContext() method to use the previous applicationArguments, environment, listeners and printedbinder objects to prepare for refreshing;
  11. Call the refreshContext() method to refresh the context object;
  12. Call the afterRefresh() method to execute the refreshed method;
  13. The stopWatch object records the end time and prints out its initialization time;
  14. Call the started method of the listener object, and call the listener to initialize the successful operation;
  15. Call the callRunners method to execute the run methods of ApplicationRunner and CommandLineRunner;
  16. Call the running method of the object listeners to perform some operations at runtime;
  17. Return the context object to end the process.

Next, we start our own analysis of the startup process from the first step.

1.StopWatch effect

First, take a look at some source codes of this class:

public class StopWatch {
    private final String id;
    private boolean keepTaskList = true;
    private final List<TaskInfo> taskList = new LinkedList<>();
    private long startTimeMillis;
    @Nullable
    private String currentTaskName;
    @Nullable
    private TaskInfo lastTaskInfo;
    private int taskCount;
    private long totalTimeMillis;
    public StopWatch() {
       this("");
    }
    public StopWatch(String id) {
       this.id = id;
    }
    public void start() throws IllegalStateException {
       start("");
    }
    public void start(String taskName) throws IllegalStateException {
       if (this.currentTaskName != null) {
          throw new IllegalStateException("Can't start StopWatch: " +
                  "it's already running");
       }
       this.currentTaskName = taskName;
       this.startTimeMillis = System.currentTimeMillis();
    }
    public void stop() throws IllegalStateException {
       if (this.currentTaskName == null) {
          throw new IllegalStateException("Can't stop StopWatch: " +
                  "it's not running");
       }
       long lastTime = System.currentTimeMillis() - this.startTimeMillis;
       this.totalTimeMillis += lastTime;
       this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
       if (this.keepTaskList) {
          this.taskList.add(this.lastTaskInfo);
       }
       ++this.taskCount;
       this.currentTaskName = null;
    }
}
class StartupInfoLogger {

   private final Class<?> sourceClass;

   StartupInfoLogger(Class<?> sourceClass) {
      this.sourceClass = sourceClass;
   }
    public void logStarted(Log log, StopWatch stopWatch) {
       if (log.isInfoEnabled()) {
          log.info(getStartedMessage(stopWatch));
       }
    }
    private StringBuilder getStartedMessage(StopWatch stopWatch) {
       StringBuilder message = new StringBuilder();
       message.append("Started ");
       message.append(getApplicationName());
       message.append(" in ");
       message.append(stopWatch.getTotalTimeSeconds());
       try {
          double uptime = 
              ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;
          message.append(" seconds (JVM running for " + uptime + ")");
       }
       catch (Throwable ex) {
          // No JVM time available
       }
       return message;
    }
}

After calling the start and stop methods, StopWatch will record the initial time and the total time spent. Then the logStarted method in the StartupInfoLogger class will be invoked in the run method. The getStartedMessage will be called in the method, and the initialization time will be printed in the log. Is it familiar to see the content spliced by getstartedmessage method?

2.getRunListeners method

The method source code is as follows:

public class SpringApplication {
    private SpringApplicationRunListeners getRunListeners(String[] args) {
       Class<?>[] types = new Class<?>[] { SpringApplication.class, 
               String[].class };
       return new SpringApplicationRunListeners(logger,
             getSpringFactoriesInstances(SpringApplicationRunListener.class,
                     types, this, args));
    }
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, 
            Class<?>[] parameterTypes, Object... args) {
       ClassLoader classLoader = getClassLoader();
       // Use names and ensure unique to protect against duplicates
       Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader
               .loadFactoryNames(type, classLoader));
       List<T> instances = createSpringFactoriesInstances(type, 
               parameterTypes, classLoader, args, names);
       AnnotationAwareOrderComparator.sort(instances);
       return instances;
    }
    private <T> List<T> createSpringFactoriesInstances(Class<T> type, 
            Class<?>[] parameterTypes,
      ClassLoader classLoader, Object[] args, Set<String> names) {
       List<T> instances = new ArrayList<>(names.size());
       for (String name : names) {
          try {
             Class<?> instanceClass = ClassUtils.forName(name, classLoader);
             Assert.isAssignable(type, instanceClass);
             Constructor<?> constructor = instanceClass
                     .getDeclaredConstructor(parameterTypes);
             T instance = (T) BeanUtils.instantiateClass(constructor, args);
             instances.add(instance);
          }
          catch (Throwable ex) {
             throw new IllegalArgumentException("Cannot instantiate " + 
                     type + " : " + name, ex);
          }
       }
       return instances;
    }
}

From the perspective of call chain, the main function of this method process is to pringFactoriesLoader.loadFactoryNames () the class name read by the method is traversed in turn and instantiated by reflection, followed by sequence return.

2.1 S pringFactoriesLoader.loadFactoryNames () method

So the key of this method is s pringFactoriesLoader.loadFactoryNames () method, the source code of this method is as follows:

public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = 
            "META-INF/spring.factories";
    private static final Log logger = LogFactory
            .getLog(SpringFactoriesLoader.class);
    private static final Map<ClassLoader, MultiValueMap<String, String>> 
            cache = new ConcurrentReferenceHashMap<>();
    private SpringFactoriesLoader() {
    }
    public static List<String> loadFactoryNames(Class<?> factoryClass, 
            @Nullable ClassLoader classLoader) {
       String factoryClassName = factoryClass.getName();
       return loadSpringFactories(classLoader)
               .getOrDefault(factoryClassName, Collections.emptyList());
    }
    private static Map<String, List<String>> loadSpringFactories(
            @Nullable ClassLoader classLoader) {
       MultiValueMap<String, String> result = cache.get(classLoader);
       if (result != null) {
          return result;
       }
    
       try {
          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);
             Properties properties = PropertiesLoaderUtils
                     .loadProperties(resource);
             for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryClassName = ((String) entry.getKey()).trim();
                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);
       }
    }
}

According to the source code, we can know that the process of this method is basically to put all META-INF/spring.factories All path files are read out. After reading out, traverse these URLs and read out the contents of their files line by line, put them into the cache and return the results at the same time.

spring.factories This file is very important. When we use it, the types that seem to be automatically configured are actually configured in this file. For example, if which framework needs to be automatically configured into springboot, you need to put the auto configuration class into the file and specify which class to perform the auto configuration operation. Even when we need to customize some classes for specific operations in a certain phase at startup, we can put the implementation classes into this file.

3.SpringApplicationRunListeners class

This class has instance objects listeners in the run method, which in turn call the starting, environmentPrepared, contextPrepared, started, failed and running methods, and call different ApplicationListener implementation classes in different methods. Generally speaking, in the spring.factories In the file, the normal process will have the following types of listeners:

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

In these listeners, the ApplicationListener interface is implemented. The source code of the interface is as follows:

public interface ApplicationListener<E extends ApplicationEvent> 
        extends EventListener {
   void onApplicationEvent(E event);
}

Therefore, it is necessary to know what functions these listeners have implemented. Just look at the onApplicationEvent method of the implementation class.

4.prepareEnvironment method

The source code of this method is as follows:

public class SpringApplication {
    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
       // Create and configure the environment
       ConfigurableEnvironment environment = getOrCreateEnvironment();
       configureEnvironment(environment, applicationArguments
               .getSourceArgs());
       listeners.environmentPrepared(environment);
       bindToSpringApplication(environment);
       if (!this.isCustomEnvironment) {
          environment = new EnvironmentConverter(getClassLoader())
                  .convertEnvironmentIfNecessary(environment,
                          deduceEnvironmentClass());
       }
       ConfigurationPropertySources.attach(environment);
       return environment;
    }
    private ConfigurableEnvironment getOrCreateEnvironment() {
       if (this.environment != null) {
          return this.environment;
       }
       switch (this.webApplicationType) {
       case SERVLET:
          return new StandardServletEnvironment();
       case REACTIVE:
          return new StandardReactiveWebEnvironment();
       default:
          return new StandardEnvironment();
       }
    }
    protected void configureEnvironment(ConfigurableEnvironment environment,
            String[] args) {
       if (this.addConversionService) {
          ConversionService conversionService = 
                  ApplicationConversionService.getSharedInstance();
          environment.setConversionService(
                  (ConfigurableConversionService) conversionService);
       }
       configurePropertySources(environment, args);
       configureProfiles(environment, args);
    }
    protected void bindToSpringApplication(
            ConfigurableEnvironment environment) {
       try {
          Binder.get(environment).bind("spring.main", 
                  Bindable.ofInstance(this));
       }
       catch (Exception ex) {
          throw new IllegalStateException("Cannot bind to" +
                  " SpringApplication", ex);
       }
    }
}

The general process of the method is as follows:

  1. Call getOrCreateEnvironment method to determine which type of environment object ConfigurableEnvironment to initialize according to webApplicationType type. In general web programs, it is SERVLET type, so the environment object type is StandardServletEnvironment;
  2. Call the configureEnvironment method. If the args array is not empty and has a value when the run method is called, the property object of SimpleCommandLinePropertySource is created, the instruction is added to the object, and set to the environment object; if the spring.profiles.active Configuration file, the file is added to the member property activeProfiles of the environment object;
  3. call listeners.environmentPrepared() method, which is very interesting. In this method, the SpringApplicationRunListener object that has been read in the environment object will be traversed in turn and its listening and triggering event method will be called. In this process, the listening object of configfileapplicationlister plays an important role: in the file path classpath:, classp ath:config/ , file:./ , file:./config/ Read yaml, yml, xml and properties files named application into the propertySourceList object of environment object for subsequent use;
  4. Call the bindToSpringApplication method to get the spring.main The configuration is bound to the source of spring application;
  5. Call configur ationPropertySources.attach () method, add the configurationProperties file to the environment object.

5.configureIgnoreBeanInfo method

The method source code is as follows:

public class SpringApplication {
    private void configureIgnoreBeanInfo(
            ConfigurableEnvironment environment) {
       if (System.getProperty(CachedIntrospectionResults
               .IGNORE_BEANINFO_PROPERTY_NAME) == null) {
          Boolean ignore = environment.getProperty("spring.beaninfo.ignore",
                  Boolean.class, Boolean.TRUE);
          System.setProperty(CachedIntrospectionResults
                  .IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
       }
    }
}

This method is mainly to spring.beaninfo.ignore Value reading. According to the official meaning, the function of this attribute is to repeatedly access the non-existent Bean information by ClassLoader, which may cause too much cost when accessing the class startup or delayed loading. It needs to be set to true to prevent this situation. The default value is false, and the spring boot method is set to true manually. Is it an official patch? Or a clever way to run a project faster?

6.printBanner method

The source code of the method is as follows:

public class SpringApplication {
    private Banner printBanner(ConfigurableEnvironment environment) {
       if (this.bannerMode == Banner.Mode.OFF) {
          return null;
       }
       ResourceLoader resourceLoader = (this.resourceLoader != null) ? 
               this.resourceLoader : new DefaultResourceLoader(
                       getClassLoader());
       SpringApplicationBannerPrinter bannerPrinter = 
               new SpringApplicationBannerPrinter(resourceLoader, 
                       this.banner);
       if (this.bannerMode == Mode.LOG) {
          return bannerPrinter.print(environment, 
                  this.mainApplicationClass, logger);
       }
       return bannerPrinter.print(environment, this.mainApplicationClass, 
               System.out);
    }
}
class SpringApplicationBannerPrinter {
    static final String BANNER_LOCATION_PROPERTY = 
            "spring.banner.location";
    static final String BANNER_IMAGE_LOCATION_PROPERTY = 
            "spring.banner.image.location";
    static final String DEFAULT_BANNER_LOCATION = "banner.txt";
    static final String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };
    private static final Banner DEFAULT_BANNER = new SpringBootBanner();
    private final ResourceLoader resourceLoader;
    private final Banner fallbackBanner;
    public Banner print(Environment environment, 
            Class<?> sourceClass, Log logger) {
       Banner banner = getBanner(environment);
       try {
          logger.info(createStringFromBanner(banner, environment, 
                  sourceClass));
       }
       catch (UnsupportedEncodingException ex) {
          logger.warn("Failed to create String for banner", ex);
       }
       return new PrintedBanner(banner, sourceClass);
    }
    private String createStringFromBanner(Banner banner, 
            Environment environment, Class<?> mainApplicationClass)
            throws UnsupportedEncodingException {
       ByteArrayOutputStream baos = new ByteArrayOutputStream();
       banner.printBanner(environment, mainApplicationClass, 
               new PrintStream(baos));
       String charset = environment.getProperty("spring.banner.charset", 
               "UTF-8");
       return baos.toString(charset);
    }
    private Banner getBanner(Environment environment) {
       Banners banners = new Banners();
       banners.addIfNotNull(getImageBanner(environment));
       banners.addIfNotNull(getTextBanner(environment));
       if (banners.hasAtLeastOneBanner()) {
          return banners;
       }
       if (this.fallbackBanner != null) {
          return this.fallbackBanner;
       }
       return DEFAULT_BANNER;
    }
    private Banner getImageBanner(Environment environment) {
       String location = environment
               .getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
       if (StringUtils.hasLength(location)) {
          Resource resource = this.resourceLoader.getResource(location);
          return resource.exists() ? new ImageBanner(resource) : null;
       }
       for (String ext : IMAGE_EXTENSION) {
          Resource resource = this.resourceLoader.getResource("banner." 
                  + ext);
          if (resource.exists()) {
             return new ImageBanner(resource);
          }
       }
       return null;
    }
    private Banner getTextBanner(Environment environment) {
       String location = environment.getProperty(BANNER_LOCATION_PROPERTY, 
               DEFAULT_BANNER_LOCATION);
       Resource resource = this.resourceLoader.getResource(location);
       if (resource.exists()) {
          return new ResourceBanner(resource);
       }
       return null;
    }
    public Banner print(Environment environment, Class<?> sourceClass, 
            PrintStream out) {
       Banner banner = getBanner(environment);
       banner.printBanner(environment, sourceClass, out);
       return new PrintedBanner(banner, sourceClass);
    }
}
class SpringBootBanner implements Banner {

   private static final String[] BANNER = { "", "  .   ____          _            __ _ _",
         " /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\", "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
         " \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )", "  '  |____| .__|_| |_|_| |_\\__, | / / / /",
         " =========|_|==============|___/=/_/_/_/" };

   private static final String SPRING_BOOT = " :: Spring Boot :: ";

   private static final int STRAP_LINE_SIZE = 42;

   @Override
   public void printBanner(Environment environment, Class<?> sourceClass, 
           PrintStream printStream) {
      for (String line : BANNER) {
         printStream.println(line);
      }
      String version = SpringBootVersion.getVersion();
      version = (version != null) ? " (v" + version + ")" : "";
      StringBuilder padding = new StringBuilder();
      while (padding.length() < STRAP_LINE_SIZE - (version.length() + 
              SPRING_BOOT.length())) {
         padding.append(" ");
      }

      printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT,
              AnsiColor.DEFAULT, padding.toString(), AnsiStyle.FAINT, version));
      printStream.println();
   }

}

First, see the printBanner method flow in the SpringApplication class:

  1. If bannerMode is OFF, jump out of the method directly; otherwise, execute the subsequent process of the method;
  2. Get the corresponding resourceLoader object;
  3. If bannerMode is LOG log printing mode, transfer the logger LOG printing object to call the printing logic;
  4. Finally, print the banner call print on the console System.out .

Again, if bannerMode is the process method of LOG mode, print:

  1. Call getbanner method to get banner object;
    1. First get spring.banner.image.location configuration path file. If it exists, instantiate the ImageBanner object;
    2. Recapture spring.banner.location If the configured path file exists, instantiate the ResourceBanner object. If it does not exist, the default value is banner.txt Documents;
    3. If neither of the previous two steps obtains the object, then judge whether the fallbackBanner is empty, and if not, return the fallbackBanner object;
    4. Returns an object of type SpringBootBanner if fallbackBanner is empty.
  2. Call the createStringFromBanner method, in which the banner.printBanner , banner.txt Files or other objects will be formatted as stream objects in this method, and the data in them will be finally converted into a String object;
  3. call logger.info Print banner information in the log;
  4. Print banner on System.out , which also calls banner.printBanner method;
  5. Calling the print method will eventually encapsulate the banner and sourceClass as PrintedBanner objects.

7.createApplicationContext method

The method source code is as follows:

public class SpringApplication {
    public static final String DEFAULT_CONTEXT_CLASS = 
            "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";
    public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = 
            "org.springframework.boot."
            + "web.servlet.context." +
            "AnnotationConfigServletWebServerApplicationContext";
    public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = 
            "org.springframework."
            + "boot.web.reactive.context." +
            "AnnotationConfigReactiveWebServerApplicationContext";
    protected ConfigurableApplicationContext createApplicationContext() {
       Class<?> contextClass = this.applicationContextClass;
       if (contextClass == null) {
          try {
             switch (this.webApplicationType) {
             case SERVLET:
                contextClass = 
                        Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
             case REACTIVE:
                contextClass = 
                        Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
             default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
             }
          }
          catch (ClassNotFoundException ex) {
             throw new IllegalStateException(
                   "Unable create a default ApplicationContext, " + 
                           "please specify an ApplicationContextClass", ex);
          }
       }
       return (ConfigurableApplicationContext) BeanUtils
               .instantiateClass(contextClass);
    }
    static WebApplicationType deduceFromClasspath() {
       if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && 
               !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && 
                   !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
          return WebApplicationType.REACTIVE;
       }
       for (String className : SERVLET_INDICATOR_CLASSES) {
          if (!ClassUtils.isPresent(className, null)) {
             return WebApplicationType.NONE;
          }
       }
       return WebApplicationType.SERVLET;
    }
    static WebApplicationType deduceFromApplicationContext(
            Class<?> applicationContextClass) {
       if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, 
               applicationContextClass)) {
          return WebApplicationType.SERVLET;
       }
       if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, 
               applicationContextClass)) {
          return WebApplicationType.REACTIVE;
       }
       return WebApplicationType.NONE;
    }
}

The main function of this method is to determine the type of webApplicationType object. To create a specific context type according to the type, the methods to determine the type of webApplicationType are the deduceFromClasspath method and the deduceFromApplicationContext method. However, the deduceFromClasspath method is used in the startup process. The final type is SERVLET, so annotatio Nconfigservletwebserverapplicationcontext will be the object type returned by this method.

8.prepareContext method

The method source code is as follows:

public class SpringApplication {
    private void prepareContext(ConfigurableApplicationContext context, 
            ConfigurableEnvironment environment,
            SpringApplicationRunListeners listeners, 
            ApplicationArguments applicationArguments, 
            Banner printedBanner) {
       context.setEnvironment(environment);
       postProcessApplicationContext(context);
       applyInitializers(context);
       listeners.contextPrepared(context);
       if (this.logStartupInfo) {
          logStartupInfo(context.getParent() == null);
          logStartupProfileInfo(context);
       }
       // Add boot specific singleton beans
       ConfigurableListableBeanFactory beanFactory = 
               context.getBeanFactory();
       beanFactory.registerSingleton("springApplicationArguments", 
               applicationArguments);
       if (printedBanner != null) {
          beanFactory.registerSingleton("springBootBanner", printedBanner);
       }
       if (beanFactory instanceof DefaultListableBeanFactory) {
          ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this
                    .allowBeanDefinitionOverriding);
       }
       // Load the sources
       Set<Object> sources = getAllSources();
       Assert.notEmpty(sources, "Sources must not be empty");
       load(context, sources.toArray(new Object[0]));
       listeners.contextLoaded(context);
    }
}

There is nothing to say about the details of the method. The general process of the method is as follows:

  1. Set the environment of the context context;
  2. Call the postProcessApplicationContext method, which has three main functions: setting the beanNameGenerator, resourceLoader and ApplicationConversionService;
  3. Call the applyInitializers method to execute the object method initialize in the initializers array;
  4. Call the method contextPrepared of SpringApplicationRunListeners;
  5. If logStartupInfo is true, the start-up and running of spring boot and the existence of active profiles will be printed;
  6. Register springApplicationArguments and springBootBanner objects in beanFactory;
  7. Call getAllSources to get primarySources and sources. In this process, only the Application class is in the process;
  8. Call the load method to load according to the obtained sources;
  9. Call the contextLoaded method of SpringApplicationRunListeners.

9.refreshContext method

Its source code is as follows:

public class SpringApplication {
    private void refreshContext(ConfigurableApplicationContext context) {
       refresh(context);
       if (this.registerShutdownHook) {
          try {
             context.registerShutdownHook();
          }
          catch (AccessControlException ex) {
             // Not allowed in some environments.
          }
       }
    }
    protected void refresh(ApplicationContext applicationContext) {
       Assert.isInstanceOf(AbstractApplicationContext.class, 
               applicationContext);
       ((AbstractApplicationContext) applicationContext).refresh();
    }
}

For spring, refreshing the context is always a very important operation. Reading spring annotations and initializing spring managed bean s are completed in this process.

For spring boot, this process can depend on the Configuration in spring boot autoconfiguration package spring boot autoconfiguration spring.factories The content of the file EnableAutoConfiguration series class is used to complete the automatic integration with various framework functions. It only needs a simple annotation and a specified custom Configuration in the Configuration file of springboot. This process is similar to the process of reading @ Configuration by spring. It is mainly realized by @ Import annotation, and then with several main annotations mentioned in point 5 of Chapter 2, different conditions are injected to achieve flexible collocation. Its specific implementation can see the parsing process analysis of the ConfigurationClassPostProcessor class.

At the same time, spring boot also integrates the tomcat container automatically. When refreshing the context, spring will instantiate the tomcat container object initially according to the user's custom configuration. The reading of the custom configuration has been analyzed previously. Next we see several rewritten methods that are called in the onRefresh method of spring:

public class ServletWebServerApplicationContext 
        extends GenericWebApplicationContext
        implements ConfigurableWebServerApplicationContext {
    public static final String DISPATCHER_SERVLET_NAME = 
            "dispatcherServlet";
    @Override
    protected void onRefresh() {
       super.onRefresh();
       try {
          createWebServer();
       }
       catch (Throwable ex) {
          throw new ApplicationContextException("Unable to start web" +
                  " server", ex);
       }
    }
    private void createWebServer() {
       WebServer webServer = this.webServer;
       ServletContext servletContext = getServletContext();
       if (webServer == null && servletContext == null) {
          ServletWebServerFactory factory = getWebServerFactory();
          this.webServer = factory.getWebServer(getSelfInitializer());
       }
       else if (servletContext != null) {
          try {
             getSelfInitializer().onStartup(servletContext);
          }
          catch (ServletException ex) {
             throw new ApplicationContextException("Cannot initialize" +
                     " servlet context", ex);
          }
       }
       initPropertySources();
    }
    protected ServletWebServerFactory getWebServerFactory() {
       String[] beanNames = getBeanFactory()
               .getBeanNamesForType(ServletWebServerFactory.class);
       if (beanNames.length == 0) {
          throw new ApplicationContextException("Unable to start" +
                  " ServletWebServerApplicationContext due to missing "
                  + "ServletWebServerFactory bean.");
       }
       if (beanNames.length > 1) {
          throw new ApplicationContextException("Unable to start" +
                  " ServletWebServerApplicationContext due to multiple " +
                  "ServletWebServerFactory beans : " + 
                  StringUtils.arrayToCommaDelimitedString(beanNames));
       }
       return getBeanFactory().getBean(beanNames[0], 
               ServletWebServerFactory.class);
    }
    @Override
    protected void initPropertySources() {
       ConfigurableEnvironment env = getEnvironment();
       if (env instanceof ConfigurableWebEnvironment) {
          ((ConfigurableWebEnvironment) env)
                  .initPropertySources(this.servletContext, null);
       }
    }
    @Override
    protected void finishRefresh() {
       super.finishRefresh();
       WebServer webServer = startWebServer();
       if (webServer != null) {
          publishEvent(new ServletWebServerInitializedEvent(webServer, 
                  this));
       }
    }
    @Override
    protected void onClose() {
       super.onClose();
       stopAndReleaseWebServer();
    }
} 

The specific call order of the refresh method in the AbstractApplicationContext class will not be described in detail. The approximate order of calling the servlet context rewrite method of springboot integration is as follows:

  1. Call the onRefresh method to create a specific tomcat container object. When creating a tomcat object, call the getWebServer method of the object factory class ServletWebServerFactory to create an instance object;
  2. Call the finishRefresh method to start the tomcat container object;
  3. Call the onclose method when the spring container is closed.

Next, the process of creating a tomcat container is analyzed in detail:

  1. First, call the onRefresh method of the refresh method in spring to complete the process of the method in the parent class;
  2. Call the createWebServer method to create the tomcat container;
  3. If the webServer object and servletContext object are empty, the getWebServerFactory method is called to get the ServletWebServerFactory to generate the webServer factory. The details of the method are as follows:
    1. Get the implementation class of ServletWebServerFactory object from the spring factory, and only one of its implementation classes can exist at the same time. Otherwise, an exception will be thrown directly;
    2. The implementation class of ServletWebServerFactory gets the object from the spring factory, which means that the spring factory has loaded the webServer factory object. It's easy to see that autoconfigure spring.factories The automatic injection class ServletWebServerFactoryAutoConfiguration has been configured in the file, in which the servletwebserver has been introduced with @ Import annotation FactoryConfiguration.EmbeddedT Omcat, if the tomcat package exists, the service factory of tomcat will be injected in. At the same time, it can be seen that the automatic configuration class also has custom property classes TomcatServletWebServerFactoryCustomizer and ServletWebServerFactoryCustomizer;
    3. Call the getBean method. When reading the @ Configuration and @ Import annotation data before calling the method, spring has instantiated the TomcatServletWebServerFactoryCustomizer and ServletWebServerFactoryCustomizer objects. Set the custom properties in the object. When instantiating the ServletWebServerFactory object, because both objects implement WebServerFactoryCustomizer Interface, so the BeanPostProcessor interface postProcessBeforeInitialization method will be called in the instantiation process, and its call chain will call the customize method of ServletWebServerFactoryCustomizer class. In this method, the container properties such as port, address and contextPath will be set for the ConfigurableServletWebServerFactory object, so the object obtained by getBean will be through spring The container is the product of initialization through the BeanPostProcessor interface. For example, the easy port port in this example has been changed to 8081;
  4. Call the getWebServer method of the object factory to get the customized container object;
  5. Call the initPropertySources method to replace the properties servletContextInitParams and servletConfigInitParams;
  6. Call the start method of webServer to start the container;
  7. Issue the servlet webserverinitializedevent container initialization success event;
  8. If the spring container is closed, the onClose method is called to close the webServer container.

In the above process, we will continue to analyze the key source part.

9.1 automatic integration principle of Tomcat container

① . ServletWebServerFactoryAutoConfiguration class

This class is in the spring boot autoconfigure package spring.factories The file is declared by EnableAutoConfiguration, and the key code for initialization of tomcat container is as follows:

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration
        .BeanPostProcessorsRegistrar.class,
      ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
      ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
      ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
   @Bean
   public ServletWebServerFactoryCustomizer 
           servletWebServerFactoryCustomizer(ServerProperties 
                   serverProperties) {
      return new ServletWebServerFactoryCustomizer(serverProperties);
   }
   @Bean
   @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
   public TomcatServletWebServerFactoryCustomizer 
           tomcatServletWebServerFactoryCustomizer(
                   ServerProperties serverProperties) {
      return new TomcatServletWebServerFactoryCustomizer(serverProperties);
   }
}

You can see that there is @ Import annotation on this class, so when loading the Bean of this class, the class referenced by the annotation will be loaded first, and then the Bean in this class will be read and loaded. The second method in the class is that only the Tomcat class exists in the spring container will be executed, so special processing will only be carried out for Tomcat.

②.ServletWebServer FactoryConfiguration.EmbeddedTomcat class

Some of the source codes are as follows:

@Configuration
class ServletWebServerFactoryConfiguration {

   @Configuration
   @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
   @ConditionalOnMissingBean(value = ServletWebServerFactory.class, 
           search = SearchStrategy.CURRENT)
   public static class EmbeddedTomcat {
      @Bean
      public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
         return new TomcatServletWebServerFactory();
      }
   }
}

It can be seen that this class will execute when the ServletWebServerFactory has not been created, so it is guaranteed that there is only one container factory in the spring container. When the Tomcat container factory is created, two custom objects in the ServletWebServerFactoryAutoConfiguration class will be called to perform attribute customization on the factory.

③ . ServerProperties class

Some source codes of this class are as follows:

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
   private Integer port;

   private InetAddress address;

   @NestedConfigurationProperty
   private final ErrorProperties error = new ErrorProperties();
   private Boolean useForwardHeaders;
   private String serverHeader;
   private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8);
   private Duration connectionTimeout;
}

You can see the annotation @ ConfigurationProperties, which indicates that the prefix is server, and then matches with the specific member property name, such as server.port , or server:port This kind of application.yml The configuration in the file will be assigned to the corresponding member properties.

④ . ServletWebServerFactoryCustomizer class

Some of the source codes are as follows:

public class ServletWebServerFactoryCustomizer
      implements WebServerFactoryCustomizer
              <ConfigurableServletWebServerFactory>, Ordered {
   private final ServerProperties serverProperties;
   public ServletWebServerFactoryCustomizer(
           ServerProperties serverProperties) {
      this.serverProperties = serverProperties;
   }
   @Override
   public int getOrder() {
      return 0;
   }
   @Override
   public void customize(ConfigurableServletWebServerFactory factory) {
      PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
      map.from(this.serverProperties::getPort).to(factory::setPort);
      map.from(this.serverProperties::getAddress).to(factory::setAddress);
      map.from(this.serverProperties.getServlet()::getContextPath)
              .to(factory::setContextPath);
      map.from(this.serverProperties.getServlet()
              ::getApplicationDisplayName).to(factory::setDisplayName);
      map.from(this.serverProperties.getServlet()::getSession)
              .to(factory::setSession);
      map.from(this.serverProperties::getSsl).to(factory::setSsl);
      map.from(this.serverProperties.getServlet()::getJsp)
              .to(factory::setJsp);
      map.from(this.serverProperties::getCompression)
              .to(factory::setCompression);
      map.from(this.serverProperties::getHttp2)
              .to(factory::setHttp2);
      map.from(this.serverProperties::getServerHeader)
              .to(factory::setServerHeader);
      map.from(this.serverProperties.getServlet()::getContextParameters)
              .to(factory::setInitParameters);
   }
}

From the source code in the class, you can see the following two information:

  • getOrder method returns 0 fixedly, so the execution order of this class is batch execution according to the default order;
  • The attribute of the member in ServerProperties is not empty, and is assigned to the corresponding attribute in WebServerFactory. The concrete manifestation is that when the getWebServerFactory method is called in the onRefresh method, the attributes of the factory object obtained are all changed. application.yml The server property configured in the file.

At this point, spring boot uses autoconfigure package to automatically configure tomcat container, which is basically completed. To integrate other frameworks with spring boot, it is also to write a XXXAutoConfiguration class, define a XXXProperties class to indicate which custom properties are supported, and then initialize other frameworks in AutoConfiguration or the declared Bean.

Let's use this similar pattern to see how spring boot can automatically integrate spring MVC and Mybaits.

9.2 automatic integration principle of spring MVC

In the spring boot autoconfigure package spring.factories The file shows the dispatcher servlet autoconfiguration class, which is the autoconfiguration class for automatic integration of spring MVC. Next, let's see how it is loaded and managed by the spring container. The premise of looking at this section is to understand the integration of spring MVC and spring. You can first familiarize yourself with the notes on the integration principles of spring MVC and spring framework.

①.DispatcherServletAutoConfiguration

Some key source codes are as follows:

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
   public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = 
           "dispatcherServlet";
   public static final String 
           DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = 
                   "dispatcherServletRegistration";
   @Configuration
   @Conditional(DefaultDispatcherServletCondition.class)
   @ConditionalOnClass(ServletRegistration.class)
   @EnableConfigurationProperties({ HttpProperties.class, 
           WebMvcProperties.class })
   protected static class DispatcherServletConfiguration {
      private final HttpProperties httpProperties;
      private final WebMvcProperties webMvcProperties;
      public DispatcherServletConfiguration(HttpProperties httpProperties,
              WebMvcProperties webMvcProperties) {
         this.httpProperties = httpProperties;
         this.webMvcProperties = webMvcProperties;
      }
      @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
      public DispatcherServlet dispatcherServlet() {
         DispatcherServlet dispatcherServlet = new DispatcherServlet();
         dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties
                 .isDispatchOptionsRequest());
         dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties
                 .isDispatchTraceRequest());
         dispatcherServlet
               .setThrowExceptionIfNoHandlerFound(this.webMvcProperties
                       .isThrowExceptionIfNoHandlerFound());
         dispatcherServlet.setEnableLoggingRequestDetails(this
                 .httpProperties.isLogRequestDetails());
         return dispatcherServlet;
      }
      @Bean
      @ConditionalOnBean(MultipartResolver.class)
      @ConditionalOnMissingBean(name = 
              DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
      public MultipartResolver multipartResolver(
              MultipartResolver resolver) {
         return resolver;
      }
   }
   @Configuration
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {
       private final WebMvcProperties webMvcProperties;
       private final MultipartConfigElement multipartConfig;
       public DispatcherServletRegistrationConfiguration(
               WebMvcProperties webMvcProperties,
               ObjectProvider<MultipartConfigElement> 
                       multipartConfigProvider) {
          this.webMvcProperties = webMvcProperties;
          this.multipartConfig = multipartConfigProvider.getIfAvailable();
       }
       @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
       @ConditionalOnBean(value = DispatcherServlet.class, name = 
               DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
       public DispatcherServletRegistrationBean 
               dispatcherServletRegistration(
                       DispatcherServlet dispatcherServlet) {
          DispatcherServletRegistrationBean registration = 
                  new DispatcherServletRegistrationBean(dispatcherServlet,
                  this.webMvcProperties.getServlet().getPath());
          registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
          registration.setLoadOnStartup(this.webMvcProperties.getServlet()
                  .getLoadOnStartup());
          if (this.multipartConfig != null) {
             registration.setMultipartConfig(this.multipartConfig);
          }
          return registration;
       }
    }
}

First, you can see several annotations of this class, and then see their functions in turn:

  • The ConditionalOnWebApplication annotation indicates that this class works when the type of web program is SERVLET;
  • The ConditionalOnClass annotation indicates that only the DispatcherServlet class has the function in the package;
  • The AutoConfigureAfter annotation indicates that the class executes after the ServletWebServerFactoryAutoConfiguration class is automatically configured.

Through these three annotations, it is determined that the automatic annotation class will only be executed under the three conditions of SERVLET type web application environment, project referencing dispatcher SERVLET and Tomcat container automatic annotation completion, which ensures the environment required by spring MVC.

Next, we see the DispatcherServletConfiguration class, which is annotated by @ Conditional, and its value is DefaultDispatcherServletCondition, which is used to determine whether the bean factory contains the dispatcherServlet object. The dispatcher Servlet configuration class uses the HttpProperties and WebMvcProperties configuration properties to declare the forward Servlet object that initializes spring MVC and a default MultipartResolver object.

We already have dispatcher Servlet in Spring factory. Next, just like Spring MVC web.xml The configuration of DispatcherServlet can be added to the file. Now you need to see the implementation subclass of the abstract class RegistrationBean, DispatcherServletRegistrationBean. In the DispatcherServletRegistrationConfiguration class, if the DispatcherServlet class exists and If it already exists in the bean factory, the method dispatcherServletRegistration is executed to register the DispatcherServlet in the ServletContext, to web.xml The effect of configuring the Spring MVC core Servlet in the.

Now everything is ready, like web.xml The file's servlet configuration has been completed, and the DispathcerServlet class is also managed by the Spring container. Now, only one chance is needed to initialize the servlet. This opportunity is a configuration or an access: this configuration refers to Spring.mvc.servlet . load on startup is set to be greater than - 1, that is, the dispatcher servlet is initialized immediately after the Tomcat container is initialized. One access means that when load on startup is the default value of - 1, the dispatcher servlet needs to be initialized by accessing the Tomcat container through the url, while the specific Spring MVC initializes D The process of ispatcherselvet is not analyzed in detail.

Next, take a look at the parent and interface details of the dispatcher servlet registration bean class implementation.

② . RegistrationBean class

The class source code is as follows:

public abstract class RegistrationBean 
        implements ServletContextInitializer, Ordered {
   private static final Log logger = LogFactory
           .getLog(RegistrationBean.class);
   private int order = Ordered.LOWEST_PRECEDENCE;
   private boolean enabled = true;
   @Override
   public final void onStartup(ServletContext servletContext) 
           throws ServletException {
      String description = getDescription();
      if (!isEnabled()) {
         logger.info(StringUtils.capitalize(description) + 
                 " was not registered (disabled)");
         return;
      }
      register(description, servletContext);
   }
   protected abstract String getDescription();
   protected abstract void register(String description, 
           ServletContext servletContext);
   public void setEnabled(boolean enabled) {
      this.enabled = enabled;
   }
   public boolean isEnabled() {
      return this.enabled;
   }
   public void setOrder(int order) {
      this.order = order;
   }
   @Override
   public int getOrder() {
      return this.order;
   }
}

This class implements the ServletContextInitializer interface, which calls the onStartup method when the Tomcat container starts, and the register method in this method. This method registers the Servlet into the ServletContext in the form of code, so as to realize the implementation of the web.xml The effect of Servlet mapping is configured in.

③ . DynamicRegistrationBean class

In its implementation subclass DynamicRegistrationBean, the register method is implemented. The key source code is as follows:

public abstract class DynamicRegistrationBean
        <D extends Registration.Dynamic> extends RegistrationBean {
    @Override
    protected final void register(String description, 
            ServletContext servletContext) {
       D registration = addRegistration(description, servletContext);
       if (registration == null) {
          logger.info(
                StringUtils.capitalize(description) + 
                " was not registered " + "(possibly already registered?)");
          return;
       }
       configure(registration);
    }
    protected abstract D addRegistration(String description, 
            ServletContext servletContext);
    protected void configure(D registration) {
       registration.setAsyncSupported(this.asyncSupported);
       if (!this.initParameters.isEmpty()) {
          registration.setInitParameters(this.initParameters);
       }
    }
}

In the register method, addRegistration method and configure method are called, which are rewritten in the subclass.

④ . ServletRegistrationBean class

The method class and method source code involved are as follows:

public class ServletRegistrationBean<T extends Servlet> 
        extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
    protected ServletRegistration.Dynamic addRegistration(
            String description, ServletContext servletContext) {
       String name = getServletName();
       return servletContext.addServlet(name, this.servlet);
    }
    protected void configure(ServletRegistration.Dynamic registration) {
       super.configure(registration);
       String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
       if (urlMapping.length == 0 && this.alwaysMapUrl) {
          urlMapping = DEFAULT_MAPPINGS;
       }
       if (!ObjectUtils.isEmpty(urlMapping)) {
          registration.addMapping(urlMapping);
       }
       registration.setLoadOnStartup(this.loadOnStartup);
       if (this.multipartConfig != null) {
          registration.setMultipartConfig(this.multipartConfig);
       }
    }
}

As you can see, the addRegistration method calls directly ServletContext.addServlet Method to add a servlet. Of course, the urlMappings property of the servlet is finished when the constructor is called. You can see it if you are interested. After adding the serlvet, the configure method will be called, which will set the default urlMapping to / *, and add attributes such as mapping, loadOnStartup and multipartConfig to the servlet.

9.3 automatic integration of mybatis

As mentioned earlier, there must be a XXXAutoConfiguration class for the automatic integration of Mybatis and spring boot. This class is in the spring.factories The file is called MybatisAutoConfiguration.

① . MybatisAutoConfiguration class

The source code of this class is as follows:

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, 
        SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
  private static final Logger logger = 
          LoggerFactory.getLogger(MybatisAutoConfiguration.class);
  private final MybatisProperties properties;
  private final Interceptor[] interceptors;
  private final ResourceLoader resourceLoader;
  private final DatabaseIdProvider databaseIdProvider;
  private final List<ConfigurationCustomizer> configurationCustomizers;
  public MybatisAutoConfiguration(MybatisProperties properties,
          ObjectProvider<Interceptor[]> interceptorsProvider,
          ResourceLoader resourceLoader,
          ObjectProvider<DatabaseIdProvider> databaseIdProvider,
          ObjectProvider<List<ConfigurationCustomizer>> 
                  configurationCustomizersProvider) {
    this.properties = properties;
    this.interceptors = interceptorsProvider.getIfAvailable();
    this.resourceLoader = resourceLoader;
    this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    this.configurationCustomizers = configurationCustomizersProvider
            .getIfAvailable();
  }
  @PostConstruct
  public void checkConfigFileExists() {
    if (this.properties.isCheckConfigLocation() && 
            StringUtils.hasText(this.properties.getConfigLocation())) {
      Resource resource = this.resourceLoader.getResource(this.properties
              .getConfigLocation());
      Assert.state(resource.exists(), "Cannot find config location: " + 
              resource+ " (please add config file or check your Mybatis" +
              " configuration)");
    }
  }
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) 
          throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this
              .properties.getConfigLocation()));
    }
    Configuration configuration = this.properties.getConfiguration();
    if (configuration == null && 
            !StringUtils.hasText(this.properties.getConfigLocation())) {
      configuration = new Configuration();
    }
    if (configuration != null && 
            !CollectionUtils.isEmpty(this.configurationCustomizers)) {
      for (ConfigurationCustomizer customizer : 
              this.configurationCustomizers) {
        customizer.customize(configuration);
      }
    }
    factory.setConfiguration(configuration);
    if (this.properties.getConfigurationProperties() != null) {
      factory.setConfigurationProperties(this.properties
              .getConfigurationProperties());
    }
    if (!ObjectUtils.isEmpty(this.interceptors)) {
      factory.setPlugins(this.interceptors);
    }
    if (this.databaseIdProvider != null) {
      factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
      factory.setTypeAliasesPackage(this.properties
              .getTypeAliasesPackage());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
      factory.setTypeHandlersPackage(this.properties
              .getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties
              .resolveMapperLocations());
    }
    return factory.getObject();
  }
  @Bean
  @ConditionalOnMissingBean
      public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory 
              sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) {
      return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
      return new SqlSessionTemplate(sqlSessionFactory);
    }
  }
  public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, ImportBeanDefinitionRegistrar, 
              ResourceLoaderAware {
    private BeanFactory beanFactory;
    private ResourceLoader resourceLoader;
    @Override
    public void registerBeanDefinitions(AnnotationMetadata 
            importingClassMetadata, BeanDefinitionRegistry registry) {
      logger.debug("Searching for mappers annotated with @Mapper");
      ClassPathMapperScanner scanner = 
              new ClassPathMapperScanner(registry);
      try {
        if (this.resourceLoader != null) {
          scanner.setResourceLoader(this.resourceLoader);
        }
        List<String> packages = AutoConfigurationPackages.get(
                this.beanFactory);
        if (logger.isDebugEnabled()) {
          for (String pkg : packages) {
            logger.debug("Using auto-configuration base package '{}'", 
                    pkg);
          }
        }
        scanner.setAnnotationClass(Mapper.class);
        scanner.registerFilters();
        scanner.doScan(StringUtils.toStringArray(packages));
      } catch (IllegalStateException ex) {
        logger.debug("Could not determine auto-configuration package," +
                " automatic mapper scanning disabled.", ex);
      }
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) 
            throws BeansException {
      this.beanFactory = beanFactory;
    }
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
      this.resourceLoader = resourceLoader;
    }
  }
  @org.springframework.context.annotation.Configuration
  @Import({ AutoConfiguredMapperScannerRegistrar.class })
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration {
    @PostConstruct
    public void afterPropertiesSet() {
      logger.debug("No {} found.", MapperFactoryBean.class.getName());
    }
  }
}

First, we can see several annotation functions of the MybatisAutoConfiguration class:

  • The ConditionalOnClass annotation indicates that SqlSessionFactoryBean and SqlSessionFactory will be executed only when there are two classes in the project;
  • The ConditionalOnBean annotation indicates that only the bean factory has a DataSource class to execute;
  • The EnableConfigurationProperties annotation indicates the property configuration automatically injected into MybatisProperties;
  • The AutoConfigureAfter annotation indicates execution after the DataSourceAutoConfiguration execution.

The first four comments let the mybatis autoconfiguration execution environment ensure that the mybatis package is introduced, the correct DataSource connection is created, the mybatis property configuration has been read, and the DataSource has been automatically injected into the bean factory, so as to meet the requirements of integrating mybatis with normal spring.

There are two ways to use springboot and mybatis integration annotation. One is to add @ MapperScan annotation to @ Configuration class. The other is to set @ Mapper annotation for each Mapper. The two ways are MapperScannerRegistrar and Autoconfigured MapperScannerRegistrar. Both of these classes implement the importbeandefinitionregister interface, and invoke in the spring refresh method process The ebeanfactorypostprocessor method is prior to the subsequent initialization of @ bean, which is the bean method finishBeanFactoryInitialization. The registerBeanDefinitions method that calls the importbeandefinitionregister interface is executed in the invokebeanfactorypostprocessor method call chain.

For the two annotations @ mapper and @ MapperScan, the two kinds of search methods are based on the ClassPathMapperScanner, but @ mapper is based on the annotation, while @ MapperScan is based on the package scope. After the annotation is found, @ MapperScan will package each mapper as beandefinition, and then use MapperFactoryBean class to further encapsulate. As for mappe How the rfactorybean class works can be seen in the integration article of mybatis and spring.

When initializing sqlsessionfactory, because of the ConditionalOnMissingBean annotation, make sure that the bean will be initialized only once, not many times. The main function of this method is to assign DataSource to SqlSessionFactoryBean object, then assign attributes such as mapperLocations and configLocation in MybatisProperties class to objects, and finally call getObject method of SqlSessionFactory. In this method, afterPropertiesSet method will be executed and the process of mybatis initialization will be implemented.

After that, SqlSessionFactory will be used to create SqlSessionTemplate. At this point, the two core beans required by mybatis will be loaded and initialized. Later, @ Autowired or manual getBean will be used in the program to inject these two beans into MapperFactoryBean class.

As long as you are familiar with the integration process of spring and mybatis, the integration process of springboot and mybatis is easy to understand, and the specific details are not described too much. So far, the integration of springboot and mybatis has been completed.

10.afterRefresh method

The method source code is as follows:

public class SpringApplication {
    protected void afterRefresh(ConfigurableApplicationContext context, 
            ApplicationArguments args) {
    }
}

The method body of this method is empty, and visual inspection should be used to give developers a series of custom operations after the context is refreshed successfully.

11.callRunners method

The method source code is as follows:

public class SpringApplication {
    private void callRunners(ApplicationContext context, 
            ApplicationArguments args) {
       List<Object> runners = new ArrayList<>();
       runners.addAll(context.getBeansOfType(ApplicationRunner.class)
               .values());
       runners.addAll(context.getBeansOfType(CommandLineRunner.class)
               .values());
       AnnotationAwareOrderComparator.sort(runners);
       for (Object runner : new LinkedHashSet<>(runners)) {
          if (runner instanceof ApplicationRunner) {
             callRunner((ApplicationRunner) runner, args);
          }
          if (runner instanceof CommandLineRunner) {
             callRunner((CommandLineRunner) runner, args);
          }
       }
    }
}

You can see that the method is very simple, just calling the ApplicationRunner interface and CommandLineRunner interface respectively. Next, the impact of the subsequent code on the main process is minimal, so we do not do too much analysis, and we will analyze it in detail after encountering the confusion in this respect.

 

Topics: Spring Tomcat Mybatis SpringBoot