avatar

13.再谈SpringBoot启动配置原理

0.概述

在前面第一篇、第二篇都已经简要的分析了下SpringBoot启动配置的原理。接下来在这篇中,我们会再次深入一点,回过头,继续看源码!废话不多说,开整把~ ~ ~

1.还是从程序入口点看起

为了直观感受和更好的理解代码逻辑,我们现在SpringBoot启动程序处下个断点。然后以debug模式跑起来。哦,对了。为了减少不必要的组件启动,这次我们只引入了web模块。


程序跑起来后,断下来,进入run方法进行分析。代码如下:

1
2
3
4
5
6
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
//先创建SpringApplication,然后再调用他的run方法。
//primarySources为我们启动类
return new SpringApplication(primarySources).run(args);
}

2.接下来我们分别看看具体干了些什么

  1. 创建SpringApplication

    • 源码如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      public class SpringApplication {

      public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
      + "annotation.AnnotationConfigApplicationContext";

      public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
      + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

      private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
      "org.springframework.web.context.ConfigurableWebApplicationContext" };

      public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
      + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

      private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
      + "web.reactive.DispatcherHandler";

      private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
      + "web.servlet.DispatcherServlet";
      //构造方法
      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 = deduceWebApplicationType();
      //设置Initializer。通过getSpringFactoriesInstances()得到需要设置的Initializer。方法如下
      setInitializers((Collection) getSpringFactoriesInstances(
      ApplicationContextInitializer.class));
      //设置ApplicationListener。通过getSpringFactoriesInstances()从/META-INF/spring.factories中读取所有的ApplicationListener
      setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
      //从堆栈中找出main函数所在的类,方法体如下
      this.mainApplicationClass = deduceMainApplicationClass();
      }

      //判断环境
      private WebApplicationType deduceWebApplicationType() {
      //REACTIVE类型。REACTIVE_WEB_ENVIRONMENT_CLASS和MVC_WEB_ENVIRONMENT_CLASS都是常量,定义在上头
      if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
      && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
      return WebApplicationType.REACTIVE;
      }
      //普通类型 WEB_ENVIRONMENT_CLASSES定义在上头
      for (String className : WEB_ENVIRONMENT_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
      return WebApplicationType.NONE;
      }
      }
      return WebApplicationType.SERVLET;
      }

      //getSpringFactoriesInstances()
      private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
      Class<?>[] parameterTypes, Object... args) {
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
      //使用SpringFactoriesLoader.loadFactoryNames()从/META-INF/spring.factories中读取所有的
      //ApplicationContextInitializer。截图在下方
      Set<String> names = new LinkedHashSet<>(
      SpringFactoriesLoader.loadFactoryNames(type, classLoader));
      List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
      classLoader, args, names);
      AnnotationAwareOrderComparator.sort(instances);
      return instances;
      }
      //找到拥有main函数的启动类
      private Class<?> deduceMainApplicationClass() {
      try {
      //获取堆栈信息
      StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
      //遍历堆栈,寻找有main方法的类,并且把它加载到内存
      for (StackTraceElement stackTraceElement : stackTrace) {
      if ("main".equals(stackTraceElement.getMethodName())) {
      return Class.forName(stackTraceElement.getClassName());
      }
      }
      }
      catch (ClassNotFoundException ex) {
      // Swallow and continue
      }
      return null;
      }
      }
      • /META-INF/spring.factories中所有的ApplicationContextInitializer。这里以SpringBoot的jar包为例

        内容如下:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        org.springframework.context.ApplicationContextInitializer=\
        org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
        org.springframework.boot.context.ContextIdApplicationContextInitializer,\
        org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
        org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

        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
    • 总结

      在SpringBoot启动时,会先创建SpringApplication实例。在SpringApplication构造方法中做了如下几件事:

      1. 首先会判断当前环境
      2. 通过getSpringFactoriesInstances()方法,从类路径下/META-INF/spring.factories文件中获取所有的ApplicationContextInitializer,加入容器(setInitializers)
      3. 和上面一样,读取类路径下/META-INF/spring.factories文件中获取所有的ApplicationListener,加入到容器中(setListeners)
      4. 从堆栈中找出main函数所在的类,加载到内存

    以上就是new SpringApplication() 的分析。接下来就差run方法了。

  2. 调用run方法

    • 源码如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      191
      public ConfigurableApplicationContext run(String... args) {
      //好像是创建计时器,统计时间,这个我们不关心
      StopWatch stopWatch = new StopWatch();
      stopWatch.start();
      ConfigurableApplicationContext context = null;
      Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
      //这个看源码好像是设置系统属性。我们也不关心
      configureHeadlessProperty();
      //getRunListeners()方法在下面,大致意识是还是读取类路径/META-INF/spring.factories中所有SpringApplicationRunListener
      //该类路径指的是SpringBoot的jar包目录下
      SpringApplicationRunListeners listeners = getRunListeners(args);
      //循环调用SpringApplicationRunListener的starting方法。代码如下
      listeners.starting();
      try {
      //处理控制台传过来的参数。
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      //字面意识是准备环境。在准备完成后,会调用Listener的environmentPrepared方法。代码如下
      ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
      configureIgnoreBeanInfo(environment);
      //打印banner图
      Banner printedBanner = printBanner(environment);
      //根据环境,创建Spring上下文。代码如下:
      context = createApplicationContext();
      //从类路径下/META-INF/spring.factories中获取所有SpringBootExceptionReporter
      exceptionReporters = getSpringFactoriesInstances(
      SpringBootExceptionReporter.class,
      new Class[] { ConfigurableApplicationContext.class }, context);
      //预备上下文环境,在初始化应用后,会调用所有Initializer的initializer()方法,再接着会调用Listener的contextPrepared方法。在准备环境完成后,调用Listener的contextLoaded方法(),告知Context已经加载完毕。代码以及介绍如下:
      prepareContext(context, environment, listeners, applicationArguments,
      printedBanner);
      //刷新容器,ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat).这个就不多说了。如果有需要请看第一篇和第二篇文章
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass)
      .logStarted(getApplicationLog(), stopWatch);
      }
      //调用所有listeners的started方法。具体方法如下:
      listeners.started(context);
      //获取所有ApplicationRunner和CommandLineRunner并循环调用,方法体如下:
      callRunners(context, applicationArguments);
      }
      catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
      }

      try {
      //调用所有listener的running方法。方法体如下
      listeners.running(context);
      }
      catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
      }
      //返回Spring的上下文
      return context;
      }

      //--------------------------------------以下为某些方法的方法体(按出场顺序)---------------------------------------

      //getRunListeners()方法。获取springBoot jar包下META-INF/spring.factories中的所有SpringApplicationRunListener
      private SpringApplicationRunListeners getRunListeners(String[] args) {
      Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
      return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
      SpringApplicationRunListener.class, types, this, args));
      }

      //listeners.starting()方法,循环调用starting()
      public void starting() {
      for (SpringApplicationRunListener listener : this.listeners) {
      listener.starting();
      }
      }

      //准备环境(prepareEnvironment).环境准备完成后,会调用Listener的environmentPrepared方法,告知环境初始化完成
      private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
      // Create and configure the environment
      ConfigurableEnvironment environment = getOrCreateEnvironment();
      configureEnvironment(environment, applicationArguments.getSourceArgs());
      //调用Listener的environmentPrepared方法,告知环境初始化完成
      listeners.environmentPrepared(environment);

      bindToSpringApplication(environment);
      if (this.webApplicationType == WebApplicationType.NONE) {
      environment = new EnvironmentConverter(getClassLoader())
      .convertToStandardEnvironmentIfNecessary(environment);
      }
      ConfigurationPropertySources.attach(environment);
      return environment;
      }
      //根据环境创建Spring上下文
      protected ConfigurableApplicationContext createApplicationContext() {
      Class<?> contextClass = this.applicationContextClass;
      if (contextClass == null) {
      try {
      switch (this.webApplicationType) {
      case SERVLET:
      contextClass = Class.forName(DEFAULT_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);
      }

      //预备上下文环境
      private void prepareContext(ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {
      //为上下文设置环境
      context.setEnvironment(environment);
      postProcessApplicationContext(context);
      //翻译的字面意识是应用初始化,具体方法如下
      applyInitializers(context);
      //应用初始化后,调用Listener的contextPrepared方法
      listeners.contextPrepared(context);
      if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
      }

      // Add boot specific singleton beans
      context.getBeanFactory().registerSingleton("springApplicationArguments",
      applicationArguments);
      if (printedBanner != null) {
      context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
      }

      // Load the sources
      Set<Object> sources = getAllSources();
      Assert.notEmpty(sources, "Sources must not be empty");
      load(context, sources.toArray(new Object[0]));
      //在准备环境完成后,调用Listener的contextLoaded方法(),告知Context已经加载完毕
      listeners.contextLoaded(context);
      }
      //应用初始化.初始化完毕后,会调用所有Initializer的initializer()方法
      protected void applyInitializers(ConfigurableApplicationContext context) {
      //拿到所有的Initializer遍历
      for (ApplicationContextInitializer initializer : getInitializers()) {
      Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
      initializer.getClass(), ApplicationContextInitializer.class);
      Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
      //循环调用Initializer的initializer方法()
      initializer.initialize(context);
      }
      }

      //listeners.started(context);循环调用所有Listener的started方法
      public void started(ConfigurableApplicationContext context) {
      for (SpringApplicationRunListener listener : this.listeners) {
      listener.started(context);
      }
      }
      //callRunners()
      private void callRunners(ApplicationContext context, ApplicationArguments args) {
      List<Object> runners = new ArrayList<>();
      //获取所有ApplicationRunner和CommandLineRunner
      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);
      }
      }
      }

      //running
      public void running(ConfigurableApplicationContext context) {
      for (SpringApplicationRunListener listener : this.listeners) {
      listener.running(context);
      }
      }
    • 总结

      SpringBoot在run方法中主要做了如下几件事

      1. 从SpringBoot这个jar包下,获取/META-INFO/spring.factories中获取所有SpringApplicationRunListeners
      2. 循环调用SpringApplicationRunListener.starting()方法
      3. 处理控制台传过来的参数。
      4. 准备环境。在准备完成后,会调用Listener.environmentPrepared()方法
      5. 打印banner图
      6. 根据环境,创建Spring上下文
      7. 从类路径下/META-INF/spring.factories中获取所有SpringBootExceptionReporter
      8. 预备上下文环境,在初始化应用后,先获取所有的Initializer,然后会调用所有Initializer.initializer()方法,再接着会调用Listener.contextPrepared()方法。在准备环境完成后,调用Listener.contextLoaded()方法,告知Context已经加载完毕。
      9. 刷新容器,ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat),初始化bean等操作
      10. 调用所有listeners.started()方法
      11. 获取所有ApplicationRunnerCommandLineRunner并循环调用
      12. 调用所有listener.running方法
      13. 返回Spring上下文(SpringContext)

    3.总结

    以上就是SpringBoot启动配置原理了。再来个全文总结。以下类是我们在这一篇,需要注意的,以后再coding过程中也会用到的。

    1. 配置在META-INF/spring.factories
    2. ApplicationContextInitializer
    3. ApplicationListener
    4. SpringApplicationRunListener

    好了,以上就是本篇的内容。感谢大家!有问题可以留言,不论是批评还是请教,都非常感谢大家~ ~ ~, 下一篇将带来自定义starter,敬请期待 ~ ~ ~


评论