2.4 执行原理
每个 Spring Boot 项目都有一个主程序启动类,在主程序启动类中有一个启动项目的 main()方法,在该方法中通过执行 SpringApplication.run()即可启动整个 Spring Boot 程序。
问题:那么 SpringApplication.run()方法到底是如何做到启动 Spring Boot 项目的呢?
下面我们查看 run()方法内部的源码,核心代码具体如下:
“`java
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
“`
“`java
public static ConfigurableApplicationContext run(Class<?> primarySource,
String… args) {
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
“`
从上述源码可以看出,SpringApplication.run()方法内部执行了两个操作,分别是 SpringApplication 实例的初始化创建和调用 run()启动项目,这两个阶段的实现具体说明如下
**1.SpringApplication 实例的初始化创建 **
查看 SpringApplication 实例对象初始化创建的源码信息,核心代码具体如下
“`java
public SpringApplication(ResourceLoader resourceLoader, Class… primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, “PrimarySources must not be null”);
// 把项目启动类.class 设置为属性存储起来
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
// 判断当前 webApplicationType 应用的类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 设置初始化器(Initializer), 最后会调用这些初始化器
this.setInitializers(this.getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 设置监听器(Listener)
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
// 用于推断并设置项目 main()方法启动的主程序启动类
this.mainApplicationClass = this.deduceMainApplicationClass();
“`
从上述源码可以看出,SpringApplication 的初始化过程主要包括 4 部分,具体说明如下。
(1)this.webApplicationType = WebApplicationType.deduceFromClasspath()
用于判断当前 webApplicationType 应用的类型。deduceFromClasspath()方法用于查看 Classpath 类路径下是否存在某个特征类,从而判断当前 webApplicationType 类型是 SERVLET 应用(Spring 5 之前的传统 MVC 应用)还是 REACTIVE 应用(Spring 5 开始出现的 WebFlux 交互式应用)
(2)this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))
用于 SpringApplication 应用的初始化器设置。在初始化器设置过程中,会使用 Spring 类加载器 SpringFactoriesLoader 从 META-INF/spring.factories 类路径下的 META-INF 下的 spring.factores 文件中获取所有可用的应用初始化器类 ApplicationContextInitializer。
(3)this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))
用于 SpringApplication 应用的监听器设置。监听器设置的过程与上一步初始化器设置的过程基本一样,也是使用 SpringFactoriesLoader 从 META-INF/spring.factories 类路径下的 META-INF 下的 spring.factores 文件中获取所有可用的监听器类 ApplicationListener。
(4)this.mainApplicationClass = this.deduceMainApplicationClass()
用于推断并设置项目 main()方法启动的主程序启动类
**2.项目的初始化启动 **
分析完 (new SpringApplication(primarySources)).run(args) 源码前一部分 SpringApplication 实例对象的初始化创建后,查看 run(args)方法执行的项目初始化启动过程,核心代码具体如下:
“`java
public ConfigurableApplicationContext run(String… args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
// 第一步:获取并启动监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments =
new DefaultApplicationArguments(args);
// 第二步:根据 SpringApplicationRunListeners 以及参数来准备环境
ConfigurableEnvironment environment =
this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
// 准备 Banner 打印器 – 就是启动 Spring Boot 的时候打印在 console 上的 ASCII 艺术字体
Banner printedBanner = this.printBanner(environment);
// 第三步:创建 Spring 容器
context = this.createApplicationContext();
exceptionReporters =
this.getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[]{ConfigurableApplicationContext.class}, new Object[]{context});
// 第四步:Spring 容器前置处理
this.prepareContext(context, environment, listeners,
applicationArguments, printedBanner);
// 第五步:刷新容器
this.refreshContext(context);
// 第六步:Spring 容器后置处理
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if(this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass))
.logStarted(this.getApplicationLog(), stopWatch);
}
// 第七步:发出结束执行的事件
listeners.started(context);
// 返回容器
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters,
(SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
“`
从上述源码可以看出,项目初始化启动过程大致包括以下部分:
– 第一步:获取并启动监听器
“`
this.getRunListeners(args)和 listeners.starting()方法主要用于获取 SpringApplication 实例初始化过程中初始化的 SpringApplicationRunListener 监听器并运行。
“`
– 第二步:根据 SpringApplicationRunListeners 以及参数来准备环境
“`
this.prepareEnvironment(listeners, applicationArguments)方法主要用于对项目运行环境进行预设置,同时通过 this.configureIgnoreBeanInfo(environment)方法排除一些不需要的运行环境
“`
– 第三步:创建 Spring 容器
“`
根据 webApplicationType 进行判断,确定容器类型,如果该类型为 SERVLET 类型,会通过反射装载对应的字节码,也就是 AnnotationConfigServletWebServerApplicationContext,接着使用之前初始化设置的 context(应用上下文环境)、environment(项目运行环境)、listeners(运行监听器)、applicationArguments(项目参数)和 printedBanner(项目图标信息)进行应用上下文的组装配置,并刷新配置
“`
– 第四步:Spring 容器前置处理
“`
这一步主要是在容器刷新之前的准备动作。设置容器环境,包括各种变量等等,其中包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础
“`
– 第五步:刷新容器
“`
开启刷新 spring 容器,通过 refresh 方法对整个 IOC 容器的初始化(包括 bean 资源的定位,解析,注册等等),同时向 JVM 运行时注册一个关机钩子,在 JVM 关机时会关闭这个上下文,除非当时它已经关闭
“`
– 第六步:Spring 容器后置处理
“`
扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束 log,或者一些其它后置处理。
“`
– 第七步:发出结束执行的事件
“`
获取 EventPublishingRunListener 监听器,并执行其 started 方法,并且将创建的 Spring 容器传进去了,创建一个 ApplicationStartedEvent 事件,并执行 ConfigurableApplicationContext 的 publishEvent 方法,也就是说这里是在 Spring 容器中发布事件,并不是在 SpringApplication 中发布事件,和前面的 starting 是不同的,前面的 starting 是直接向 SpringApplication 中的监听器发布启动事件。
“`
– 第八步:执行 Runners
“`
用于调用项目中自定义的执行器 XxxRunner 类,使得在项目启动完成后立即执行一些特定程序。其中,Spring Boot 提供的执行器接口有 ApplicationRunner 和 CommandLineRunner 两种,在使用时只需要自定义一个执行器类实现其中一个接口并重写对应的 run()方法接口,然后 Spring Boot 项目启动后会立即执行这些特定程序
“`
下面,通过一个 Spring Boot 执行流程图,让大家更清晰的知道 Spring Boot 的整体执行流程和主要启动阶段:
![image-20191226180809230](./images/image-20191226180809230.png)
## 3. SpringBoot 数据访问
SpringData 是 Spring 提供的一个用于简化数据库访问、支持云服务的开源框架。它是一个伞形项目,包含了大量关系型数据库及非关系型数据库的数据访问解决方案,其设计目的是使我们可以快速且简单地使用各种数据访问技术。Spring Boot 默认采用整合 SpringData 的方式统一处理数据访问层,通过添加大量自动配置,引入各种数据访问模板 xxxTemplate 以及统一的 Repository 接口,从而达到简化数据访问层的操作。
Spring Data 提供了多种类型数据库支持,对支持的的数据库进行了整合管理,提供了各种依赖启动器,接下来,通过一张表罗列提供的常见数据库依赖启动器,如表所示。
| 名称 | 描述 |
| spring-boot-starter-data-jpa | 使用 Spring Data JPA 与 Hibernate |
| spring-boot-starter-data-mongodb | 使用 MongoDB 和 Spring Data MongoDB |
| spring-boot-starter-data-neo4j | 使用 Neo4j 图数据库和 Spring Data Neo4j |
| spring-boot-starter-data-redis | 使用 Redis 键值数据存储与 Spring Data Redis 和 Jedis 客户端 |
除此之外,还有一些框架技术,Spring Data 项目并没有进行统一管理,Spring Boot 官方也没有提供对应的依赖启动器,但是为了迎合市场开发需求、这些框架技术开发团队自己适配了对应的依赖启动器,例如,mybatis-spring-boot-starter 支持 MyBatis 的使用