前言
本文针对Spring-boot的启动原理分析基于Spring-boot 2.1.4版本。主要分为两大模块,首先是从源代码入手进行分析,接着通过实现ApplicationContextInitializer和SpringApplicationRunListener两个接口来更好的认识整个启动流程
一、启动
Spring-boot的启动我把它分为两个部分,一是创建SpringApplication对象,二是运行SpringApplication对象的run方法。即
//核心步骤
(new SpringApplication(primarySources)).run(args);
1、创建SpringApplication对象
启动环境
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
启动环境如上,只是一个普通的Spring-boot项目,引入了WEB和AOP模块而已。
开始
我们对启动主类进行Debug
一直step into到
到这里就是我们的关键点创建SpringApplication对象和运行SpringApplication对象的run方法。
创建SpringApplication对象
接着上面step into >> this((ResourceLoader)null, primarySources); >> public SpringApplication(ResourceLoader resourceLoader, Class… primarySources)
这个构建方法就是创建SpringApplication对象的关键构造。
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");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//以上变量初始化完毕
//重点:在这里从类路径下的“MAMATA_INF/spring.factories”文件中得到所要加载的所有ApplicationContextInitializer
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//同上:在这里从类路径下的“MAMATA_INF/spring.factories”文件中得到所要加载的所有ApplicationListener
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
在运行上面的构造方法后,SpringApplication对象就创建完成。重点注意this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
和this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
这两个方法,他们分别从项目的类路径下的MAMATA_INF/spring.factories中找到所有ApplicationContextInitializer和ApplicationListener,并把它们都加载到对象中。
可以看到SpringApplication对象中的Initializer有六个,Listener有10个,它们都是在spring.factories中事先写好的。分别是:
大家可以去spring-boot-autoconfigure-2.1.4.RELEASE.jar包的META-INF\spring.factories
中查看,看是不是里面的ApplicationContextInitializer和ApplicationListener都确确实实的加载进去了。
目前SpringApplication对象已经创建好了,接下来就是运行它的run方法了。
2、运行run方法
接下来是第二部分,上一步创建好的SpringApplication调用它的run方法
public ConfigurableApplicationContext run(String... args) {
//创建一个stopwatch对象,用于记录整个应用跑起来的时间
StopWatch stopWatch = new StopWatch();
//开始记录
stopWatch.start();
//spring应用上下文
ConfigurableApplicationContext context = null;
//定义SpringApplication启动错误的回调接口
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
//设置headless,非重点
this.configureHeadlessProperty();
//从SpringFactoriesLoader的cache中获取所有的监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
//重点:对所有的监听器回调他们的starting方法
listeners.starting();
Collection exceptionReporters;
try {
//封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//重点:准备配置环境,看这里的传入的参数,有listeners,说明这个方法肯定有用到监听器的地方
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
//打印spring的banner
Banner printedBanner = this.printBanner(environment);
//创建应用上下文
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
//重点:准备应用上下文,看有listeners传入,肯定会用到监听器,其中还会用到ApplicationContextInitializer
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//refresh上下文,这个时候把我们应用注册的组件装配到上下文中。
this.refreshContext(context);
//从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调
//ApplicationRunner先回调,CommandLineRunner再回调
this.afterRefresh(context, applicationArguments);
//记录时间停止
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
//调用监听器的started方法
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
//调用监听器的running方法
listeners.running(context);
//返回应用上下文,到这里spring-boot启动完成。
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
总结一下run方法的流程:
通过上面的分析我们已经大概了解清楚了spring-boot启动流程。在整个启动过程中我们一开始提过的ApplicationContextInitializer和ApplicationListener的身影时不时出现在其中。接下来我们自定义ApplicationContextInitializer和ApplicationListener,通过他们来更好的认识整个过程。
二、实现ApplicationContextInitializer和SpringApplicationRunListener
1、 实现ApplicationContextInitializer
ApplicationContextInitializer接口如下:
package org.springframework.context;
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C var1);
}
可以看到只有一个方法需要我们实现,就是initialize(C var1)方法。他会在run方法中准备上下文的时候(this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
)通过调用applyInitializers()
方法被调用。
实现自己的ApplicationContextInitializer
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("MyApplicationContextInitializer 的initialize 方法 被调用了" );
}
}
2、实现SpringApplicationRunListener
SpringApplicationRunListener接口如下:
package org.springframework.boot;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
public interface SpringApplicationRunListener {
void starting();
void environmentPrepared(ConfigurableEnvironment environment);
void contextPrepared(ConfigurableApplicationContext context);
void contextLoaded(ConfigurableApplicationContext context);
void started(ConfigurableApplicationContext context);
void running(ConfigurableApplicationContext context);
void failed(ConfigurableApplicationContext context, Throwable exception);
}
看到这个接口是不是有一种恍然大悟的感觉。结合上面的启动流程,会发现SpringApplicationRunListener中的各个方法其实就是监听spring-boot启动的各个节点。
现在实现我们的SpringApplicationRunListener
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
//必须要有一个带有SpringApplication application, String[] args参数的构造函数
public MySpringApplicationRunListener(SpringApplication application, String[] args){
}
@Override
public void starting() {
System.out.println("MySpringApplicationRunListener 的 starting 方法被调用了>>>>应用开始初始化!");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
System.out.println("MySpringApplicationRunListener 的 environmentPrepared 方法被调用了>>>>环境准备完毕!");
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("MySpringApplicationRunListener 的 contextPrepared 方法被调用了>>>>上下文准备完毕!");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("MySpringApplicationRunListener 的 contextLoaded 方法被调用了>>>>上下文装备完毕!");
}
@Override
public void started(ConfigurableApplicationContext context) {
System.out.println("MySpringApplicationRunListener 的 started 方法被调用了>>>>应用已运行!");
}
@Override
public void running(ConfigurableApplicationContext context) {
System.out.println("MySpringApplicationRunListener 的 running 方法被调用了>>>>应用正在运行!");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("MySpringApplicationRunListener 的 sfailed 方法被调用了>>>>应用运行失败!");
}
}
让自定义的ApplicationContextInitializer和SpringApplicationRunListener生效
前面说过,ApplicationContextInitializer和SpringApplicationRunListener都是通过类路径下的META-INF/spring.factories装配的。
所以我们需要在类路径下创建META-INF/spring.factories,让spring-boot装配我们的ApplicationContextInitializer和SpringApplicationRunListener
spring.factories如下:
org.springframework.context.ApplicationContextInitializer=\
yong.com.springaop.Listener.MyApplicationContextInitializer
org.springframework.boot.SpringApplicationRunListener=\
yong.com.springaop.Listener.MySpringApplicationRunListener
运行结果
可以看到控制台的打印
可以看到我们自定义的ApplicationContextInitializer和SpringApplicationRunListener生效了。
如果我们愿意,也可以debug看一下
Initializer:
对比前面的Initializer,会发现这里多出一个我们自定义的Initializer
对于listener,我们在调用 listeners.starting();的时候可以看到
step into
可以看到调用了自定义listener的starting方法
接着控制台输出
以上便是通过实现ApplicationContextInitializer和SpringApplicationRunListener来参与到spring-boot的启动过程。到这里相信大家对spring-boot的原理已经有了初步的了解。
总结
spring-boot的启动在我看来可以简单分为两个大模块,分别是创建SpringApplication对象和调用SpringApplication对象的run方法。当然其中还有很多细节是没有全部说出来的,但是通过上面,相信我们已经能够对spring-boot的启动有了一个大致的了解。