本文将以Spring Boot启动类SpringApplication为切入点,深入剖析SpringBoot的整个启动过程,本文内容包括
- 使用方式
- 原理剖析
- 总结
使用方式
在Spring Boot官方文档里,有个简单的实例,
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.web.bind.annotation.*;
@RestController
@EnableAutoConfiguration
public class Example {
@RequestMapping("/")
String home() {
return "Hello World!";
}
public static void main(String[] args) {
SpringApplication.run(Example.class, args);
}
}
本实例启动了一个Spring Boot实例,注册了一个Restfull接口。对比没有Spring Boot之前,这种方式不仅减少了配置文件,还极大的简化了代码,并且不需要再部署到一个Web容器中,而是使用了内置的Web容器。下面我们将深入剖析这种使用的整体过程。
原理剖析
根据实例,查看SpringApplication.run()
方法的实现
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
源码中,SpringApplication分两个阶段,
- 一个是
new SpringApplication()
准备阶段。 - 一个是
SpringApplication#run
运行阶段。
接下来对这两个阶段进行剖析。
准备阶段
准备阶段就是SpringApplication
的实例化阶段,先看源码
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();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
从源码中看出,准备阶段主要完成以下几件事情:
- 推断应用类型
- 设置初始化器
- 设置监听器
- 推断带有main方法的引导类
推断应用类型
deduceWebApplicationType
方法用于推断应用类型
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
根据源码,可以看出,应用类型主要有三种,WebApplicationType.REACTIVE
、 WebApplicationType.NONE
、 WebApplicationType.SERVLET
,针对三种应用类型,创建的ApplicationContext
会有所不同,具体的不同应用类型创建何种ApplicationContext
将会在运行阶段说明。这个应用类型推断,依据应用中所存在的类来判断,判断如下:
- 若存在
org.springframework.web.reactive.DispatcherHandler
并且不存在org.springframework.web.servlet.DispatcherServlet
, 则推断为REACTIVE
应用类型。 - 若同时存在
javax.servlet.Servlet
和org.springframework.web.context.ConfigurableWebApplicationContext
, 则推断为SERVLET
应用类型。 - 否则推断为
NONE
类型
设置初始化器和监听器
设置初始化器和监听器,用了同样的原理,都是使用SpringFactoriesLoader
类进行加载的,前面有篇专门的文章介绍了SpringFactoriesLoader
,这里就简单说明下。SpringFactoriesLoader
会读取META-INF/spring.factories
文件,这个文件符合properties
文件格式,SpringFactoriesLoader
读取这里文件的内容,并将所有相同Key的字符串集合,实例化为Class
。
这里初始化器和监听器的设置,就是将所有META-INF/spring.factories
文件里的键为 org.springframework.context.ApplicationContextInitializer
的字符串集合,设置为初始化器;所有键为org.springframework.context.ApplicationListener
的字符串集合,设置为监听器。
推断引导类
deduceMainApplicationClass
方法用于推断包含main方法的引导类,下面是具体源码:
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
从源码看出,推断的方式是获取运行时堆栈,然后从堆栈中遍历方法名为main
的类,作为引导类。
以上内容为SpringApplication
的准备阶段,接下来将继续分析运行阶段内容
运行阶段
运行阶段的入口是org.springframework.boot.SpringApplication#run(java.lang.String...)
这个方法,看下源码过程
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 获取SpringApplicationRunListener 集合
SpringApplicationRunListeners listeners = getRunListeners(args);
// 通知监听器开始应用starting
listeners.starting();
try {
// 获取启动参数,并添加到环境配置中
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 初始化环境配置ConfigurableEnvironment, 并通知监听器
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 获取Banner图标
Banner printedBanner = printBanner(environment);
// 根据WebApplicationType 判断创建的ApplicationContext
context = createApplicationContext();
// 获取异常报告处理类
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 装载Bean,并执行初始化器,及通知监听器
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 执行ApplicationContext刷新操作
refreshContext(context);
// 执行刷新后操作
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 通知监听器,应用已started
listeners.started(context);
// 执行Runners,包括ApplicationRunner和CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 异常处理
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 通知监听器,应用处于running状态
listeners.running(context);
}
catch (Throwable ex) {
// 异常处理
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
运行阶段,主要完成以下流程
- 获取监听器
- 通知监听器,应用处于starting状态
- 初始化环境配置
ConfigurableEnvironment
- 创建
ApplicationContext
- 装载Bean
- 执行
ApplicationContext
refresh操作 - 通知监听器,应用处理started状态
- 执行Runners,包括
ApplicationRunner
和CommandLineRunner
两接口的所有实现 - 最后通知监听器,应用处于running状态
下面我们重点分析创建ApplicationContext
、装载Bean、执行ApplicationContext
refresh操作
创建ApplicationContext
源码中,通过createApplicationContext
方法来创建ApplicationContext
,来看下这个方法的源码如何
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);
}
源码可见,创建ApplicationContext
是根据WebApplicationType
的类型创建的,规则如下:
WebApplicationType类型 | ApplicationContext实例类型 |
---|---|
SERVLET | org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext |
REACTIVE | org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext |
NONE | org.springframework.context.annotation.AnnotationConfigApplicationContext |
装载Bean
装载Bean的过程,通过方法prepareContext
实现,代码如下
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments,
Banner printedBanner) {
// 设置全局外部化配置
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 执行初始化器
applyInitializers(context);
// 通知监听器
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 添加启动参数为单例
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// 加载配置
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 装载Bean
load(context, sources.toArray(new Object[0]));
// 通知监听器
listeners.contextLoaded(context);
}
这部分代码的主要内容在于第29行,load
方法代码如下:
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
load
方法创建了BeanDefinitionLoader
去加载所有Bean,在方法最后执行装载过程。
执行ApplicationContext
refresh操作
ApplicatoinContext
的refresh操作,通过refreshContext
方法操作,最终执行的代码如下:
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
此方法可以看出,最终执行的还是AbstractApplicationContext
的refresh方法。
总结
SpringApplication
的最简单使用,是使用SpringApplication.run
方法SpringApplication
的启动,分为两阶段,准备阶段和运行阶段- 准备阶段主要完成应用类型推断、设置初始化器和监听器、引导类推断
- 运行阶段主要完成
ApplicationContext
的创建、Bean的装载、执行ApplicationContext
的刷新 ApplicationContext
的刷新主要通过AbstractApplicationContext#refresh
完成。