SpringBoot执行原理

1、执行原理:

每一个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法, 在该方法中经过执行SpringApplication.run()便可启动整个Spring Boot程序。react

Q:web

那么SpringApplication.run()方法究竟是如何作到启动Spring Boot项目的呢?spring

@SpringBootApplication  //可以扫描Spring组件并自动配置SpringBoot
public class Springboot01DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot01DemoApplication.class, args);
    }
}

上述是一个SpringBoot的启动类,进入SpringApplication.run()方法设计模式

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

如图所示,进入了run方法后,紧接着,调用了重载方法,重载方法作了两件事:数组

  1. 实例化SpringApplication对象app

  2. 调用run方法jvm

1. 实例化SpringApplication对象

public SpringApplication(Class<?>... primarySources) {
 this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
 //......设置了一些参数....这里省略,下面是重点
    //......设置了一些参数....这里省略,下面是重点
    //......设置了一些参数....这里省略,下面是重点

 //项目启动类 SpringbootDemoApplication.class设置为属性存储起来
 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

 //设置应用类型是SERVLET应用(Spring 5以前的传统MVC应用)仍是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
 this.webApplicationType = WebApplicationType.deduceFromClasspath();

 // 设置初始化器(Initializer),最后会调用这些初始化器
 //所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新以前进行初始化的操做
 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

 // 设置监听器(Listener)
 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

 // 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
 this.mainApplicationClass = deduceMainApplicationClass();
}

SpringApplication的构造方法中,首先设置了一些参数,而后作了5件事:ide

1.1 项目启动类 SpringbootDemoApplication.class设置为属性存储起来

this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

给这个成员变量赋值,把传入的primarySources进行转换,而后赋值,这个primarySources就是咱们Springboot启动类的Main方法中传入的:
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=函数

1.2 设置应用类型是SERVLET应用(Spring 5以前的传统MVC应用)仍是REACTIVE应用

this.webApplicationType = WebApplicationType.deduceFromClasspath();

判断当前的web应用类型是servlet应用仍是reactive应用,那么如何判断的?进入.deduceFromClasspath()方法:
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=字体

  1. 首先判断类路径下Reactive相关的class是否存在,若是存在就说明当前应用是内嵌的 Reactive Web 应用。例如说,Spring Webflux 。

  2. 判断类路径下是否存在Servlet类型的类。若是不存在,则返回NONE,表示当前应用是非内嵌的 Web 应用。

  3. 不然,表示当前应用是内嵌的 Servlet Web 应用。例如说,Spring MVC 。

1.3 设置初始化器(Initializer),最后会调用这些初始化器

所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新以前进行初始化的操做.

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

这里传入了一个ApplicationContextInitializer.class

进入getSpringFactoriesInstances()方法(下图若是看不清请右键另存为):
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

这段代码主要作了以下几件事:

  1. SpringFactoriesLoader.loadFactoryNames(type, classLoader)
    这里的type就是刚才传入的,ApplicationContextInitializer.class

  2. loadFactoryNames 调用了 loadSpringFactories方法

  3. loadSpringFactories方法作了以下的事:

    Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
    LinkedMultiValueMap result = new LinkedMultiValueMap();

    判断classLoader是否为空,若是不为空加载META-INF下的spring.factories,如上图所示,根据传入的参数值(ApplicationContextInitializer.class)的类型,在spring.factories中进行查找,根据当前传入的类型找到两个类,这两个类就是初始化器:

    org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
    org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

获得这两个类后,把它们存入set去重,而后进行实例化,而后排序,最终返回,到此初始化器已经设置完成了。而后存入List<ApplicationContextInitializer<?>> initializers,等待以后使用

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

1.4 设置监听器(Listener)

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

和1.3同理,也是经过调用getSpringFactoriesInstances,只不过传递的参数发生了改变。变成了ApplicationListener.class ,因此它就是在spring.factories中根据ApplicationListener.class找,而后实例化,而后返回存入Listeners中。

1.5 初始化 mainApplicationClass 属性

用于推断并设置项目main()方法启动的主程序启动类

this.mainApplicationClass = deduceMainApplicationClass();
 private Class<?> deduceMainApplicationClass() {
  try {
      // 得到当前 StackTraceElement 数组
   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;
 }

判断哪一个类执行了main方法,而后返回。

1.6 总结

实例化SpringApplication对象作了哪些事?

  1. 项目启动类 SpringbootDemoApplication.class设置为属性存储起来

  2. 设置应用类型是SERVLET应用(Spring 5以前的传统MVC应用)仍是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)

  3. 设置初始化器(Initializer),最后会调用这些初始化器

  4. 设置监听器(Listener)

  5. 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类

2. 调用run方法

回忆一下,在SpringBoot启动类的Main方法中,执行了SpringApplication.run(Main方法所在的当前类.class, args);,这个方法主要作了两件事:

  • 实例化SpringApplication对象 (已上述)

  • 调用run方法

进入run方法:
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

run方法大致上作了9件比较重要的事。

2.1 获取并启动监听器

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
//args是启动Spring应用的命令行参数,该参数能够在Spring应用中被访问。如:--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

它其实仍是经过getSpringFactoriesInstances()这个方法来获取,这个方法已经很熟悉了, 1.3,1.4都使用到了,再也不赘述。

那么本步骤就是经过getSpringFactoriesInstances()拿到了一个SpringApplicationRunListeners类型的监听器,而后调用.starting()启动。

2.2 项目运行环境Environment的预配置

建立并配置当前SpringBoot应用将要使用的Environment,并遍历调用全部的SpringApplicationRunListener的environmentPrepared()方法

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

configureIgnoreBeanInfo(environment);
// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
Banner printedBanner = printBanner(environment);

进入prepareEnvironment()方法:
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

  1. 查询environment,有就返回,没有的话建立后返回。

  2. 配置环境

    1. PropertySources:加载执行的配置文件

    2. Profiles:多环境配置,针对不一样的环境,加载不一样的配置

  3. listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)。

  4. 将建立的环境绑定到SpringApplication对象上

  5. 是不是web环境,若是不是就转换为标准环境

  6. 配置PropertySources对它本身的递归依赖

  7. 返回

此时已经拿到了ConfigurableEnvironment 环境对象,而后执行configureIgnoreBeanInfo(environment),使其生效。

2.3 建立Spring容器

context = createApplicationContext();
// 得到异常报告器 SpringBootExceptionReporter 数组
//这一步的逻辑和实例化初始化器和监听器的同样,
// 都是经过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化全部的异常处理类。
exceptionReporters = getSpringFactoriesInstances(
      SpringBootExceptionReporter.class,
      new Class[] { ConfigurableApplicationContext.class }, context);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

根据 webApplicationType 类型,得到 ApplicationContext 类型,这里建立容器的类型 仍是根据webApplicationType进行判断的,该类型为SERVLET类型,因此会经过反射装载对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext。

而后经过getSpringFactoriesInstances()得到异常报告器。

2.4 Spring容器前置处理

这一步主要是在容器刷新以前的准备动做。包含一个很是关键的操做:将启动类注入容器,为后续开启自动化配置奠基基础。

prepareContext(context, environment, listeners, applicationArguments,
      printedBanner);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

这块会对整个上下文进行一个预处理,好比触发监听器的响应事件、加载资源、设置上下文环境等等。

2.5 刷新容器

refreshContext(context);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

  • IOC容器初始化

  • 向JVM运行时注册一个关机钩子(函数),在JVM关机时关闭这个上下文,除非它当时已经关闭。(若是jvm变关闭了,当前上下文对象也能够被关闭了)

//TODO refresh方法在springioc章节中会有详细说明(挖个坑- - )。

2.6 Spring容器后置处理

afterRefresh(context, applicationArguments);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

扩展接口,设计模式中的模板方法,默认为空实现。
若是有自定义需求,能够重写该方法。好比打印一些启动结束log,或者一些其它后置处理。

2.7 发出结束执行的事件通知

listeners.started(context);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

2.8 执行Runners运行器

callRunners(context, applicationArguments);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后当即执行一些特定程序。

Runner 运行器用于在服务启动时进行一些业务初始化操做,这些操做只在服务启动后执行一次。

Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口

2.9 发布应用上下文就绪事件

listeners.running(context);

表示在前面一切初始化启动都没有问题的状况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext.

这样整个Spring Boot项目就正式启动完成了。

2.10 返回容器

return context;

完成~

总结:

  1. 获取并启动监听器

  2. 项目运行环境Environment的预配置

  3. 建立Spring容器

  4. Spring容器前置处理

  5. 刷新容器

  6. Spring容器后置处理

  7. 发出结束执行的事件通知

  8. 执行Runners运行器

  9. 发布应用上下文就绪事件

  10. 返回容器

相关文章
相关标签/搜索