(二)Springboot原理源码解析之启动原理

目录

一、Springboot分析简介

二、将会涉及到的Springboot注解

1.@SpringBootApplication

2.@SpringBootConfiguration

3.@EnableAutoConfiguration

4.@AutoConfigurationPackage

5.XXXAutoConfiguration类中几类注解

三、源码分析之SpringApplication初始化流程

四、源码分析之SpringApplication.run方法流程

1.StopWatch作用效果

2.getRunListeners方法

2.1 SpringFactoriesLoader.loadFactoryNames()方法

3.SpringApplicationRunListeners类

4.prepareEnvironment方法

5.configureIgnoreBeanInfo方法

6.printBanner方法

7.createApplicationContext方法

8.prepareContext方法

9.refreshContext方法

9.1 Tomcat容器的自动集成原理

9.2 SpringMVC的自动集成原理

9.3 Mybatis的自动集成

10.afterRefresh方法

11.callRunners方法


在阅读本文章前最好了解Springboot框架大致组成,传送门:(一)Springboot原理源码解析之Springboot框架组成

一、Springboot分析简介

众所周知,springboot之所以如此受欢迎是因为它的两大特点:开箱即用和约定优于配置。

  1. 开箱即用:开箱即用,Outofbox,是指在开发过程中,通过在MAVEN项目的pom文件中添加相关依赖包,然后使用对应注解来代替繁琐的XML配置文件以管理对象的生命周期。此特点表现在springboot应用创建的便捷性,只需要引入springboot-starter包,即可马上使用该框架进行开发,如果要支持web或者持久化,再直接导入他们的starter包即可。
  2. 约定由于配置:约定优于配置,Convention over configuration,是一种由SpringBoot本身来配置目标结构,由开发者在结构中添加信息的软件设计范式。将spring和其它框架的各种集合配置缩减成可以是在yml文件或者properties文件中添加相应的属性即可,可以使用户引用包后直接配置几条框架属性便可以完成框架集成和自定义。

上述两点可以说是springboot的灵魂所在,任何事物都有两面性,springboot极大的加快了开发者的编码速度,但如果是框架集成时配置没配好或者类似的情况时开发者就很难去发现问题所在,并且集成时由于如此便捷以至于大部分的开发者对于集成时的细节,以及框架的某些特性、深层的原理都会疏于了解,让开发者越来越懒,这对以后的职业竞争以及职业发展都有一定的负面影响。

因此本篇便简单的分析一下springboot的启动原理,在看本篇的前提是需要对Spring启动原理、SpringMVC启动集成原理、Mybatis启动原理以及Mybatis和Spring集成原理都要有所了解,否则分析的时候其具体细节无法理解。

二、将会涉及到的Springboot注解

1.@SpringBootApplication

注解源码如下:

@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 {};

}

该注解是Springboot的启动类,其关键的注解是@SpringBootConfiguration和@EnableAutoConfiguration,第一个注解表明被注解的类是@Configuration配置类,第二个注解则表示启动自动配置。

2.@SpringBootConfiguration

该注解源码如下:

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

}

3.@EnableAutoConfiguration

该注解源码如下:

@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 {};

}

该注解内有一个@Import注解,其中有AutoConfigurationImportSelector类属性,该类的实现类是springboot启动的重要一环,会把包中的META-INF/spring.factories文件读取出来,再将其中的数据配置一一实例化供后面使用。

4.@AutoConfigurationPackage

其注解源码如下:

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

}

该注解的作用便是如果未配置基本路径,则会把被该注解注解的类当成默认路径,效果相当于@SpringBootApplication(scanBasePackages = "XXX.XXX"),XXX.XXX则是该注解注解类的位置。

5.XXXAutoConfiguration类中几类注解

例如mybatis的AutoConfiguration自动配置类便是MybatisAutoConfiguration,在该类中一般会有以下几种注解,起作用分别如下:

  • @ConditionalOnClass:classpath中存在该类时起效;
  • @ConditionalOnMissingClass:classpath中不存在该类时起效;
  • @ConditionalOnBean:DI容器中存在该类型Bean时起效;
  • @ConditionalOnMissingBean:DI容器中不存在该类型Bean时起效;
  • @ConditionalOnWebApplication:Web应用环境下起效;
  • @ConditionalOnSingleCandidate:DI容器中该类型Bean只有一个或@Primary的只有一个时起效;
  • @ConditionalOnExpression:SpEL表达式结果为true时;
  • @ConditionalOnProperty:参数设置或者值一致时起效;
  • @ConditionalOnResource:指定的文件存在时起效;
  • @ConditionalOnJndi:指定的JNDI存在时起效;
  • @ConditionalOnJava:指定的Java版本存在时起效;
  • @ConditionalOnNotWebApplication:非Web应用环境下起效。

经常使用的注解一般就前面五种,其它的在非主流框架才会被用到。

三、源码分析之SpringApplication初始化流程

其源码如下:

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;
   // 默认使用控制台模式输出
   private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
   // 展示启动时Spring图标的对象
   private Banner banner;
   private ResourceLoader resourceLoader;
   private BeanNameGenerator beanNameGenerator;
   // 环境对象,保存运行时的属性
   private ConfigurableEnvironment environment;
   // ApplicationContext类类型
   private Class<? extends ConfigurableApplicationContext> 
           applicationContextClass;
   // 记录Springboot启动类型,默认是SERVLET
   private WebApplicationType webApplicationType;
   // 启动上下文前的初始化类集合
   private List<ApplicationContextInitializer<?>> initializers;
   // 监听类集合
   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();
    }
    // 使用SpringFactoriesLoader获得class路径后实例化
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
       return getSpringFactoriesInstances(type, new Class<?>[] {});
    }
    // 使用SpringFactoriesLoader获得class路径后实例化
    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;
    }
    // 将获得的Class类型实例化
    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方法
    public static ConfigurableApplicationContext run(
            Class<?> primarySource, String... args) {
       return run(new Class<?>[] { primarySource }, args);
    }
    // 外部调用的run方法
    public static ConfigurableApplicationContext run(
            Class<?>[] primarySources, String[] args) {
       return new SpringApplication(primarySources).run(args);
    }
}

先从main函数开始,从SpringApplicationo.run()开始分析,其大致流程如下:

  1. 进入run()方法;
  2. 进入SpringApplication(arg1,arg2)构造函数;
  3. 将resourceLoader对象、primarySources赋给成员变量;
  4. 调用getSpringFactoriesInstances()方法从spring.factories获得ApplicationContextInitializer和ApplicationListener的实现子类,并分别添加进成员变量initializers和listeners;
  5. 调用deduceMainApplicationClass()方法赋值给mainApplicationClass成员变量,其值一般是调用链中调用SpringApplicationo.run()的main方法。

 

四、源码分析之SpringApplication.run方法流程

其类的run方法源码如下:

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;
    }
}

接下来分析其具体流程和作用:

  1. 声明StopWatch对象stopWatch,并调用其start方法,记录从run方法开始的时间;
  2. 调用configureHeadlessProperty方法这是headlessProperty属性;
  3. 根据传入的args调用getRunListeners方法获取SpringApplicationRunListeners对象listeners;
  4. 调用listeners.starting();
  5. 根据args初始化DefaultApplicationArguments对象applicationArguments,其构造函数中会调用到SimpleCommandLineArgsParser的parse方法,该方法是将诸如--XX=XX此类的指令解析成CommandLineArgs对象中的属性;
  6. 根据applicationArguments和listenner调用prepareEnvironment()方法初始化Environment,在其中进行了读取yml、xml以及properties文件,但并未对其中的数据进行解析;
  7. 调用printBanner()方法从environment对象中获取Bannber对象;
  8. 调用createApplicationContext方法创建相应的ApplicationContext上下文,需要注意的是context对象会是ConfigurableApplicationContext的子类;
  9. 调用getSpringFactoriesInstances方法获取SpringBootExceptionReporter的所有子类放进exceptionReporters数组中,以便抛出异常时有类处理这些异常;
  10. 调用prepareContext()方法使用前面的applicationArguments、environment、listeners以及printedBanner对象进行刷新前的准备操作;
  11. 调用refreshContext()方法刷新context对象;
  12. 调用afterRefresh()方法执行刷新后的方法;
  13. stopWatch对象记录结束时间,并将其初始化时间打印出来;
  14. 调用对象listeners的started方法,调用监听器初始化成功的操作;
  15. 调用callRunners方法,执行ApplicationRunner和CommandLineRunner的run方法;
  16. 调用对象listeners的runing方法,在运行时执行某些操作;
  17. 返回context对象,结束流程。

接下来我们从第一步开始自己的分析其启动流程。

1.StopWatch作用效果

先看一下该类的部分源码:

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;
    }
}

调用start和stop方法后,StopWatch会记录初始时间和总共花费时间,后续在run方法中调用StartupInfoLogger类中的logStarted方法,其方法中又会调用getStartedMessage,将其初始化时间打印在日志中。看到getStartedMessage方法拼接的内容是不是有点眼熟呢?

2.getRunListeners方法

其方法源码如下:

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;
    }
}

从起调用链来看,该方法流程的主要作用便是将从SpringFactoriesLoader.loadFactoryNames()方法读取出来的类名字依次遍历通过反射实例化该类,后面排个序返回。

2.1 SpringFactoriesLoader.loadFactoryNames()方法

因此该方法的关键便是SpringFactoriesLoader.loadFactoryNames()方法,该方法源码如下:

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);
       }
    }
}

根据源代码可以知道该方法的流程大致上就是把所有的META-INF/spring.factories路径文件全都读出来,读出来后遍历这些Url并将其文件里面的内容逐行读出来,放进缓存后同时返回结果。

spring.factories这个文件是非常重要的,我们在使用的时候看起来是自动配置的类型其实具体类型都被配置此文件中。例如,如果哪个框架需要自动配置进springboot,就需要将自动配置类放进该文件中并指定哪个类来执行自动配置的操作。甚至我们需要自定义一些启动时进行在某个环节进行特定操作的类时,也可以将实现类放进此文件中。

3.SpringApplicationRunListeners类

该类在run方法中有实例对象listeners,该对象依次会调用starting、environmentPrepared、contextPrepared、started、failed以及running方法,在不同的方法中会分别调用不同的ApplicationListener实现类,一般而言,在spring.factories文件中,正常流程会拥有下面几种监听器:

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

在这些监听器中,都会实现了ApplicationListener接口,接口源码如下:

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

因此需要知道这些监听器具体实现了什么功能,直接看实现类的onApplicationEvent方法即可。

4.prepareEnvironment方法

该方法源码如下:

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);
       }
    }
}

方法大致流程如下:

  1. 调用getOrCreateEnvironment方法根据webApplicationType类型来判断初始化哪种类型的环境对象ConfigurableEnvironment,在一般的web程序中一般是SERVLET类型,因此环境对象类型是StandardServletEnvironment;
  2. 调用configureEnvironment方法。如果调用run方法时args数组不为空且有值,则创建SimpleCommandLinePropertySource的属性对象,将指令添加进该对象中,并设置进环境对象中;如果存在该spring.profiles.active配置文件,则将文件添加进environment对象的成员属性activeProfiles中;
  3. 调用listeners.environmentPrepared()方法,该方法很有意思,在该方法中会依次遍历已经读取在environment对象中的SpringApplicationRunListener对象,并调用其监听触发事件方法,而在该过程中ConfigFileApplicationListener监听对象则发挥了一项至关重要的作用:在文件路径classpath:、classpath:config/、file:./、file:./config/中读取yaml、yml、xml以及properties四类以application命名的文件到environment对象的propertySourceList对象中以供后续使用;
  4. 调用bindToSpringApplication方法,将获取到的environment中的spring.main配置绑定到SpringApplication的source中;
  5. 调用ConfigurationPropertySources.attach()方法,将configurationProperties文件添加到environment对象中。

5.configureIgnoreBeanInfo方法

方法源码如下:

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());
       }
    }
}

该方法主要是对spring.beaninfo.ignore值的读取,根据官方的意思来看,该属性的作用便是为了当对不存在的Bean信息进行重复的ClassLoader访问,可能会导致在访问该类启动或延迟加载时很花费代价太大,需要设置为true来防止这种情况。其默认值是false,而springboot这里手动开个方法设置为true,是否是官方打的补丁?又或者是为了项目运行更快采取的取巧方式?

6.printBanner方法

方法涉及的源码如下:

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();
   }

}

首先看到SpringApplication类中的printBanner方法流程:

  1. 如果bannerMode是OFF直接跳出方法,否则执行方法后续流程;
  2. 获得对应的resourceLoader对象;
  3. 如果bannerMode是LOG日志打印模式,则将logger日志打印对象传进去调用打印逻辑;
  4. 最后将banner调用print打印在控制台System.out。

再看到如果bannerMode是LOG模式的流程方法print:

  1. 调用getBannber方法,获取banner对象;
    1. 首先获得spring.banner.image.location配置的路径文件,若存在则实例化ImageBanner对象;
    2. 再获得spring.banner.location配置的路径文件,若存在则实例化ResourceBanner对象,不存在则默认取banner.txt文件;
    3. 如果前面两个步骤都没有获得对象,则判断fallbackBanner是否为空,不为空返回fallbackBanner对象;
    4. 如果fallbackBanner为空则返回SpringBootBanner类型对象。
  2. 调用createStringFromBanner方法,在该方法中会调用banner.printBanner,banner.txt文件或者其它的对象将会在该方法中格式化为流对象,并将其中的数据最终转换为一个String对象;
  3. 调用logger.info将banner信息打印在日志中;
  4. 将banner打印在System.out中,其中也会调用banner.printBanner方法;
  5. 调用完print方法最终将会封装banner和sourceClass为PrintedBanner对象。

7.createApplicationContext方法

其方法源码如下:

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;
    }
}

该方法主要的作用便是判断webApplicationType对象的类型,根据类型去创建具体的上下文类型,其判断webApplicationType类型的方法便是deduceFromClasspath方法和deduceFromApplicationContext方法,但是启动流程中使用的是deduceFromClasspath方法,最终判断出来的类型时SERVLET,因此AnnotationConfigServletWebServerApplicationContext将是该方法返回的对象类型。

8.prepareContext方法

其方法源码如下:

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);
    }
}

方法的细节没什么可说的,其方法大致流程如下:

  1. 设置context上下文的environment;
  2. 调用postProcessApplicationContext方法,其中主要有三个作用,设置beanNameGenerator和resourceLoader、ApplicationConversionService;
  3. 调用applyInitializers方法执行initializers数组中的对象方法initialize;
  4. 调用SpringApplicationRunListeners的方法contextPrepared;
  5. 如果logStartupInfo为true则打印Springboot的启动、运行以及activeProfiles文件是否存在的日志;
  6. 在beanFactory注册springApplicationArguments以及springBootBanner对象;
  7. 调用方法getAllSources获得primarySources和sources,在该流程中只有Application类在其中;
  8. 根据获得的sources调用load方法加载;
  9. 调用SpringApplicationRunListeners的方法contextLoaded。

9.refreshContext方法

其源码如下:

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();
    }
}

对于spring而言,刷新上下文一直是个非常重要的操作,读取spring的各种注解以及初始化spring管理的bean都是在该流程中完成。

对于Springboot而言,该过程可以依赖配置在springboot自动配置包spring-boot-autoconfiguration中的spring.factories文件内容EnableAutoConfiguration系列类来完成与各个框架功能的自动整合,只需要一个简单的注解和在springboot的配置文件中加上其规定好的自定义配置即可完成。该流程和spring读取@Configuration的流程差不多,主要通过@Import注解实现,再搭配第二章第五点中提到的几个主要注解来实现不同条件的注入,实现灵活的搭配。其具体实现可以看到关于ConfigurationClassPostProcessor类的解析流程分析。

同时,springboot也自动集成了tomcat容器,在刷新上下文时spring也会根据用户的自定义配置去初始实例化tomcat容器对象,自定义配置的读取在前面已经分析过了。接下来我们看到spring的onRefresh方法中调用的几个被重写的方法:

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();
    }
} 

在AbstractApplicationContext类中的refresh方法具体调用顺序便不赘述,调用springboot集成的servlet上下文重写的方法的大致顺序为:

  1. 调用onRefresh方法,创建具体的tomcat容器对象,创建tomcat对象时会调用对象工厂类ServletWebServerFactory的getWebServer方法来创建实例对象;
  2. 调用finishRefresh方法,启动tomcat容器对象;
  3. 调用onclose方法,当spring容器关闭时调用。

接下来详细分析一下创建tomcat容器的流程:

  1. 先调用spring中refresh方法的onRefresh方法,完成父类中该方法的流程;
  2. 调用createWebServer方法创建tomcat容器;
  3. 如果webServer对象和servletContext对象为空,则调用getWebServerFactory方法获取ServletWebServerFactory产生webServer工厂,其方法的具体细节为:
    1. 从spring工厂中获取ServletWebServerFactory对象的实现类,且其实现类只能同时存在一个,否则将会直接抛出异常;
    2. ServletWebServerFactory的实现类是从spring工厂中获取对象的,说明至此spring工厂已经加载了webServer工厂对象,看autoconfigure不难看出在spring.factories文件中已经配置了自动注入类ServletWebServerFactoryAutoConfiguration,在其中使用@Import注解引入了ServletWebServerFactoryConfiguration.EmbeddedTomcat,如果tomcat包是存在的则会将tomcat的服务工厂注入进来,同时也可以看到该自动配置类也存在自定义属性类TomcatServletWebServerFactoryCustomizer和ServletWebServerFactoryCustomizer;
    3. 调用getBean方法,在调用该方法前读取@Configuration和@Import注解的数据时,spring已经实例化了TomcatServletWebServerFactoryCustomizer以及ServletWebServerFactoryCustomizer对象,设置自定义属性在对象中,当实例化ServletWebServerFactory对象的时候由于两个对象都实现了WebServerFactoryCustomizer接口,因此在实例化过程中会调用BeanPostProcessor接口postProcessBeforeInitialization方法,其调用链将会调用到ServletWebServerFactoryCustomizer类的customize方法,在该方法中对ConfigurableServletWebServerFactory对象设置port、address和contextPath等容器属性,因此getBean获得的对象将是经过spring容器经过BeanPostProcessor接口进行过初始化操作的产物,如该例子中容易的port端口就被改成了8081;
  4. 调用对象工厂的getWebServer方法获取具体被自定义的容器对象;
  5. 调用initPropertySources方法对属性servletContextInitParams和servletConfigInitParams进行替换;
  6. 调用webServer的start方法,启动容器;
  7. 发布ServletWebServerInitializedEvent容器初始化成功事件;
  8. 如果spring容器被关闭则调用onClose方法,关闭webServer容器。

在上述过程中所涉及到的关键源码部分后续继续分析。

9.1 Tomcat容器的自动集成原理

①.ServletWebServerFactoryAutoConfiguration类

该类是在spring-boot-autoconfigure包中的spring.factories文件被EnableAutoConfiguration声明,其中和tomcat容器初始化关键代码如下:

@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);
   }
}

可以看到这个类上有@Import注解,因此在加载该类的Bean时将会首先加载注解引用的类,然后再读取加载这个类中的Bean,类中的第二个方法是只有spring容器中存在Tomcat类才会执行,因此只针对Tomcat进行特殊的处理。

②.ServletWebServerFactoryConfiguration.EmbeddedTomcat类

其部分源码如下:

@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();
      }
   }
}

可以看到该类会在ServletWebServerFactory尚未创建的时候执行,因此保证spring容器中有且仅有一个容器工厂,当Tomcat容器工厂创建后将会调用ServletWebServerFactoryAutoConfiguration类中的两个自定义对象对该工厂进行属性自定义操作。

③.ServerProperties类

该类的部分源码如下:

@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;
}

可以看到有注解@ConfigurationProperties,其中标明了前缀为server,再搭配具体的成员属性名称,如server.port,或者server:port这种在application.yml文件中的配置将会被赋值到具体对应的成员属性中。

④.ServletWebServerFactoryCustomizer类

其部分源码如下:

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);
   }
}

从类中的源码可以窥探到下面两个信息:

  • getOrder方法固定返回0,因此该类的执行顺序是按默认顺序批次执行的;
  • 将ServerProperties中的成员属性不为空的依次赋值给WebServerFactory对应的属性,其具体表现则是当在onRefresh方法中调用完getWebServerFactory方法后所获得的factory对象其中的属性全变成了application.yml文件中配置的server属性。

至此,springboot使用autoconfigure包自动配置tomcat容器则算大致完成了,其它框架要和springboot集成基本上也是写一个XXXAutoConfiguration类,定义一个XXXProperties类表明支持哪些自定义属性,接着在AutoConfiguration中或者其中声明的Bean中完成其它框架的初始化等操作。

接下来我们使用这种类似的模式来看看springboot如何自动集成SpringMVC和Mybaits的。

9.2 SpringMVC的自动集成原理

在spring-boot-autoconfigure包中spring.factories文件看到DispatcherServletAutoConfiguration类,该类就是自动集成SpringMVC的自动配置类,接下来我们看看它是怎么被spring容器加载管理的。在看这一小节的前提是对SpringMVC和spring的集成有所了解,可以先熟悉熟悉前面SpringMVC和Spring框架集成原理的笔记。

①.DispatcherServletAutoConfiguration

其部分关键源码如下:

@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;
       }
    }
}

先看到这个类的几个注解,依次看下这几个注解的作用:

  • ConditionalOnWebApplication注解表明web程序的类型是SERVLET时该类起作用;
  • ConditionalOnClass注解表明只有DispatcherServlet类存在包中该类起作用;
  • AutoConfigureAfter注解表明该类在ServletWebServerFactoryAutoConfiguration类被自动配置后执行执行。

通过这三个注解则确定了该自动注解类只会在SERVLET类型的web应用环境、项目引用了DispatcherServlet和在Tomcat容器自动注解完成之后的三个条件下才会执行,确保了SpringMVC所需要的环境。

再接着看到DispatcherServletConfiguration类,这个类被@Conditional注解,并且其中的值为DefaultDispatcherServletCondition,作用是判断bean工厂是否含有dispatcherServlet对象。而DispatcherServletConfiguration类则是使用HttpProperties和WebMvcProperties配置属性来声明初始化SpringMVC的转发Servlet对象以及声明一个默认的MultipartResolver对象。

前面我们已经在Spring工厂中有了DispatcherServlet,接下来就只需要像SpringMVC一样在web.xml文件中加上DispatcherServlet的配置即可,此刻便需要看到抽象类RegistrationBean的实现子类DispatcherServletRegistrationBean,在DispatcherServletRegistrationConfiguration类中如果DispatcherServlet类存在并且在bean工厂中已经存在了,则执行方法dispatcherServletRegistration来将DispatcherServlet注册进ServletContext中,达到web.xml文件中配置SpringMVC核心Servlet的效果。

现在已经万事俱备,类似web.xml文件的serlvet配置已经完成,并且DispathcerServlet类也被Spring容器管理,现在只需要初始化Serlvet的一个契机。这个契机便是一个配置或者一次访问:这个配置指的是spring.mvc.servlet.load-on-startup设置为大于-1,即在初始化完Tomcat容器后立马初始化DispatcherServlet;一次访问指的是当load-on-startup为默认值-1时,需要通过url访问Tomcat容器来初始化DispatcherServlet,而具体的SpringMVC初始化DispatcherSerlvet的流程便不详细分析。

接下来看看DispatcherServletRegistrationBean类实现的父类和接口详情。

②.RegistrationBean类

类源码如下:

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;
   }
}

该类实现了ServletContextInitializer接口,该接口会在Tomcat容器启动的时候调用onStartup方法,在该方法中又会调用register方法,这个方法便是将Servlet通过代码的形式注册到SerlvetContext中的,以达到实现在web.xml中配置servlet-mapping这种形式serlvet的效果。

③.DynamicRegistrationBean类

在其实现子类DynamicRegistrationBean中,实现了register方法,其关键源码如下:

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);
       }
    }
}

在register方法中又调用了addRegistration方法和configure方法,这两个方法在子类中又被重写。

④.ServletRegistrationBean类

涉及的方法类方法源码如下:

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);
       }
    }
}

可以看到,addRegistration方法直接调用了ServletContext.addServlet方法添加servlet,当然servlet的urlMappings属性在调用构造函数即完成了,有兴趣可以看下。当添加完serlvet后会调用configure方法,该方法会设置默认的urlMapping为/*,并且为servlet添加mapping和loadOnStartup、multipartConfig之类的属性。

9.3 Mybatis的自动集成

像前面所说的那样,Mybatis和springboot自动集成必然会有一个XXXAutoConfiguration的类,该类在mybatis-spring-boot-autoconfigure包的spring.factories文件中名为MybatisAutoConfiguration。

①.MybatisAutoConfiguration类

该类的源码如下:

@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());
    }
  }
}

先看到MybatisAutoConfiguration类的几个注解作用:

  • ConditionalOnClass注解表明只有当项目中有SqlSessionFactoryBean和SqlSessionFactory两个类才会执行;
  • ConditionalOnBean注解表明只有bean工厂有DataSource类才会执行;
  • EnableConfigurationProperties注解表明自动注入MybatisProperties中的属性配置;
  • AutoConfigureAfter注解表明在DataSourceAutoConfiguration执行之后执行。

前面四个注解让MybatisAutoConfiguration执行环境确保了引入mybatis包、创建了正确的DataSource连接、mybatis属性配置已经被读取以及DataSource被自动注入进bean工厂,以达到使用普通的spring集成mybatis所需要的几个条件。

使用springboot和mybatis集成注解有两种方式,一是在@Configuration类上加上@MapperScan注解,一是对每个mapper设置@Mapper注解,两种方式处理类分别是MapperScannerRegistrar和AutoConfiguredMapperScannerRegistrar,这两个类都实现了ImportBeanDefinitionRegistrar接口,在spring的refresh方法流程中invokeBeanFactoryPostProcessors方法是比后面初始化@Bean这种bean方法finishBeanFactoryInitialization要靠前的,而调用ImportBeanDefinitionRegistrar接口的方法registerBeanDefinitions便是在invokeBeanFactoryPostProcessors方法调用链执行的。

对于@Mapper和@MapperScan这两种注解而言,两种使用搜索符合条件的类都是使用类ClassPathMapperScanner中的方法,只是@Mapper方式是根据注解来查找,而@MapperScan则是根据包范围来查找,找到之后会给每个mapper包装成BeanDefination,再使用MapperFactoryBean类来进一步封装,至于MapperFactoryBean类是如何运作可以看mybatis和spring的集成文章。

初始化SqlSessionFactory时因为有ConditionalOnMissingBean注解,因此确保该bean只会初始化一次,不会初始化多次。该方法的主要功能便是将DataSource赋给SqlSessionFactoryBean对象,再将MybatisProperties类中的mapperLocations、configLocation等属性赋给对象,最后调用SqlSessionFactory的getObject方法,在该方法中又会执行afterPropertiesSet方法,进入mybatis初始化的流程,这里便不再赘述。

而后又会使用SqlSessionFactory创建SqlSessionTemplate,至此,mybatis需要的两大核心bean则被加载初始化,后面在程序里面使用@Autowired或者手动getBean则会将这两个bean注入进MapperFactoryBean类。

只要熟悉了spring和mybatis的集成流程,springboot和mybatis的集成流程则很容易看懂了,其中具体的细节便不做过多描述,至此,springboot和mybatis的集成已经完成。

10.afterRefresh方法

其方法源码如下:

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

该方法的方法体为空,目测应该是为了给开发者在上下文刷新成功后进行一系列的自定义操作。

11.callRunners方法

方法源码如下:

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);
          }
       }
    }
}

可以看到方法十分简单,只是分别调用ApplicationRunner接口和CommandLineRunner接口。接下来后续的代码对主流程的影响微乎其微,因此便不做过多的分析,待以后碰到这方面的困惑后再来详细分析分析。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值