题外话
因工作原因,陆续开始接触spring boot,且对其源码和机制越来越发的需要深入了解。所以想系统的分析下spring boot源码,希望可以坚持下去。
spring boot启动
@SpringBootApplication
public class SimpleApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleApplication.class, args);
}
}
从最简化的spring boot启动main入口中可以看出通过SpringApplication.run(Class, String[])方法启动的。我们跟进入方法,不难发现run静态方法中构造了SpringApplication对象,然后进行run方法。
首先查看主构造方法:
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();
}
- resourceLoader从注解来看是一个资源加载loader,这里传入的是null,应该后续会使用到默认值。
- primarySources传入的是我们main方法的启动类,从这里可以看出能传入多个,这里我们传入的就一个。
- webApplicationType表示web服务类型,从这个枚举类内部代码可以看出有none,servlet,reactive三种,根据依赖包来进行判断的。
- setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)) 简单理解获取了ApplicationContextInitalizer的实现。
- setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)) 简单理解获取ApplicationListener的实现。
- mainApplicationClass通过deduceMainApplicationClass方法获取main启动类(异常类堆栈的方式),这里可以看出main启动类和primarySources对应的类可以不一样。
前面讲到了两个接口:ApplicationContextInitializer和ApplicationListener,这两个接口分别是干什么的呢。简单介绍一下:
ApplicationContextInitializer通过接口注解了解到:
- 用于在ConfigurableApplicationContext执行refresh操作之前对它进行一些初始化操作
- 通常被用作web应用,在一些程序设计在spring容器初始化使用。
- 支持Spring中的Ordered接口以及@Order注解来对多ApplicationContextInitializer实例进行排序,按照排序后的顺序依次执行回调
ApplicationListener基于观察者模式实现的一种application程序启动过程中观察程序状态过程变更的一种机制,可以自定义实现ApplicationListener用于观察ApplicationEvent或者某种Event,甚至可以自定义Event,然后手动出发publishEvent。(有点像guava的EventBus机制,只是这里落地到场景)
run
我们接下来看下SpringApplication对象的方法run()
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;
}
- StopWatch简单的看成一个stop watch的机制,保存stop的记录信息。
- configureHeadlessProperty即配置headless模式,这种模式是一种系统缺少显示设备、键盘和鼠标外设的情况模式。
- SpringApplicationListeners为SpringApplicationRunListener接口实现集合,可以理解这个接口就是在spring启动整个过程都需要回调这些listener,debug能发现,拿到了一个名为EventPublishingRunListener,这个就是用来进行触发publishEvent的被观察者。
- ConfigurableEnvironment为配置环境对象,简单理解所有的配置信息汇总在这个对象中
- Banner就是我们常在控制台输出的画面横幅,可以使用图片或者文本进行替换
- ConfigurableApplicationContext根据webApp…Type进行构造的上下文对象
- exceptionReporters为实现SpringBootExceptionReporter对象集合,可以自定义实现异常上报收集的实现等,spring-boot中有个FailureAnalyzers分析。
- 接下来进入关键步骤的第一步:prepareContext,准备容器阶段,将执行所有的initializers逻辑,做初始化准备操作。
- refreshContext,可以理解成容器初始化节点,将执行bean的创建和实例化。
- afterRefresh,容器后处理, 可以看到会找到ApplicationRunner和CommandLineRunner的实现类并执行。但从2.x版本来看,似乎这个方法是个空方法,applicationRun和commandRun移到启动最后。
- 然后根据stopwatch打印出启动时间
- 这里调用ApplicationRunner和CommandLineRunner的实现类
结语
其实到这里我们简单的分析了下,SpringApplication.run作为spring boot启动的时候的动作过程。后面我们将陆续展开讨论内部的一些过程。