由于简书的markdown不支持目录结构,更好的阅读体验可以查看对应的个人blog: https://buaazhangyk.github.io/2018/07/02/spring-boot-1/
0. 前言
开始之前,举一个使用spring boot启动web应用的代码示例:
@SpringBootApplication
public class SpringbootPracticeApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootPracticeApplication.class, args);
}
}
通过上述代码可以看到,Spring Boot 中应用启动的核心入口在SpringApplication
这个类中完成。继续查看在SpringApplication
的run
方法内部执行中,主要分为两步:1.初始化创建一个SpringApplication
,2.然后执行run(String... args)
方法对Application进行启动。
public staticConfigurableApplicationContext run(Class[] primarySources, String[] args) {
return newSpringApplication(primarySources).run(args);
}
由此,本文对spring boot启动过程的分析也会从这两部分进行展开。1)SpringApplication的初始化部分; 2)SpringApplication的run执行部分。
1. SpringApplication的初始化
1.1 SpringApplication类私有变量
下边的图简单描述了SpringApplication类所包含的一些私有变量。后续会结合类的构造函数来分析其中的重要私有变量及其左右。
1.2 SpringApplication构造函数
结合Class的构造函数来重点看一下下面几个变量的作用以及如何进行初始化的。
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();
}
1.2.1 ResourceLoader
表示Spring中用来加载资源的资源加载器。
1.2.2 webApplicationType
代表这个SpringApplication的类型,主要包括三个类型:NONE / SERVLET / REACTIVE
NONE: The application should not run as a web application and should not start an embedded web server.
SERVLET: The application should run as a servlet-based web application and should start anembedded servlet
web server.
REACTIVE: The application should run as a reactive web application and should start anembedded reactive web server.
SpringBoot是怎么知道究竟是那种类型的ApplicationType的呢?实现的代码在方法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;
}
在这段代码里一个核心的方法调用是 ClassUtils.isPresent(String className,@NullableClassLoader classLoader)
,
这个方法是判断参数中的className是否存在并可以加载成功。由此可见WebApplicationType类型的判断取决于引入的jar包。
其中,
REACTIVE_WEB_ENVIRONMENT_CLASS 对应的类为 org.springframework.web.reactive
.DispatcherHandler, 对应的package是spring-webflux
MVC_WEB_ENVIRONMENT_CLASS对应的类为org.springframework.web.servlet.DispatcherServlet , 对应的package是 spring-webmvc
WEB_ENVIRONMENT_CLASSES 对应的类为{"javax.servlet.Servlet","org.springframework.web.context
.ConfigurableWebApplicationContext” },对应的package是servlet-api和spring-web
1.2.3 ApplicationContextInitializer
用来在对ApplicationContext进行refresh操作之前对Application context进行一些初始化操作。
Callback interface for initializing a Spring {@ConfigurableApplicationContext} prior to being
{@ConfigurableApplicationContext#refresh()} refreshed.
Typically used within web applications that require some programmatic initialization of the application context.
For example, registering property sources or activating profiles against the
{@ConfigurableApplicationContext#getEnvironment()}
context's environment. See {@ContextLoader} and {@FrameworkServlet} support for declaring a "contextInitializerClasses" context-param and init-param, respectively.
通过查看代码,我们可以看到ApplicationContextInitializer的获取是通过调用 getSpringFactoriesInstances(Class<T> type)
方法得到的,这个方法实际是去寻找指定Class类型的类并将其实例化返回。那具体是从哪里找呢? 会在后边的小节单独分析一下
1.2.4 ApplicationListener
基于观察者模式的Application的事件监听器。将ApplicationListener注册到ApplicationContext中,当有对应事件发生时,监听器会被调用
Interface to be implemented by application event listeners.
Based on the standard {@java.util.EventListener} interface for the Observer design pattern.
As of Spring 3.0, an ApplicationListener can generically declare the event type that it is interested in. When
registered with a Spring ApplicationContext, events will be filtered accordingly, with the listener getting invoked for matching event objects only.
与获取ApplicationContextInitializer的过程一直, ApplicationListener的获取也是通过调用getSpringFactoriesInstances(Class<T> type)
实现。
1.2.5 mainApplicationClass
启动应用程序的main class.通过分析当前的程序堆栈信息获取。
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;
}
1.2.6 关于getSpringFactoriesInstances
2.SpringApplication的run
2.1 整体流程
run方法是SpringApplication的核心方法,在这个方法内部完成了 系统属性的注入,Runner的执行,
创建、准备以及refresh整个ApplicationContext 核心流程。大致可以将整个run方法归纳分解成6个步骤。
[图片上传失败...(image-4fd544-1531105646640)]
2.2 细节分析
下边对这六个步骤进行详细的分析和解读。
2.2.1 SpringApplicationRunListeners
SpringApplicationRunListener是事件监听器,用来监听SpringApplication
的启动过程,监听到事件发生时进行一些回调操作。通过下边的代码可以看到获取的核心方法是getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)
完成的
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
2.2.2 Prepare Environment
配置Application的环境,主要是一些property,这些属性会在创建ApplicationContext以及Referesh的时候起到左右。
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
核心流程:
创建一个ConfigurableEnvironment
配置ConfigurableEnvironment,主要配置PropertySource(包括defaultProperties和addCommandLineProperties)和Profile。
将environment绑定至SpringApplication
Attach一个ConfigurationPropertySource至environment.
2.2.3 Create ApplicationContext
根据this.webApplicationType的类型来创建不同的ConfigurableApplicationContext。
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);
}
2.2.4 Prepare Context
在 prepareContext的过程中,首先会运行一些初始化的流程,然后会注册一些spring boot启动所需要的bean,加载一些初始的beans。
//1. Apply any relevant post processing the ApplicationContext
postProcessApplicationContext(context);
//2. Apply any {@linkApplicationContextInitializer}s to the context before it isrefreshed.
applyInitializers(context);
//3. Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
//4. Load beans into the application context.
Set sources = getAllSources();
load(context, sources.toArray(newObject[0]));
2.2.5 Refresh Context
对前边两步中创建和准备好的application context执行refresh操作,这一部分的具体实现在spring-context包中。本文不再具体分析。
2.2.6 Call Runner
主要是调用一下设置的一些ApplicationRunner和CommandLineRunner。
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
3 总结
Spring boot实际上是对原有Spring Application启动方式的一种革命。
在传统的Spring Application中,程序启动的方式是传统的web容器(如tomcat、jetty)为入口,spring application作为webAppContext插入到web容器中。而在spring
boot的方式中,完全是以spring application为主,传统的web容器作为插件嵌入到spring application中。