logo头像

优秀是一种习惯

spring-boot 外部容器启动流程及原理

本文于 363 天之前发表,文中内容可能已经过时。

外部容器启动流程及原理 author: Tank

前言

springboot 嵌入式容器启动是以jar包形式,那如果采用外部容器启动就需要使用war包。

外部容器启动准备工作

1、准备一个外部容器如 (tomcat)在idea里配置好即可,这里不做多解释
2、需要新增一个servlet初始化时指定主类的一个类 (ServletInitializer 名字随意,这个是模版生成的)
1
2
3
4
5
6
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringBoot04WebJspApplication.class);
}
}
3、指定打包方式为war tomcat依赖的scope需要改成provided
1
2
3
4
5
6
7
8
9
<!--pom.xml -->
<packaging>war</packaging>


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
4、查看servlet3.0规范 Annotations and pluggability -> Shared libraries / runtimes pluggability

In addition to supporting fragments and use of annotations one of the requirements
is that not only we be able to plug-in things that are bundled in the WEB-INF/lib
but also plugin shared copies of frameworks - including being able to plug-in to the
web container things like JAX-WS, JAX-RS and JSF that build on top of the web
container. The ServletContainerInitializer allows handling such a use case
as described below.
The ServletContainerInitializer class is looked up via the jar services API.
For each application, an instance of the ServletContainerInitializer is
created by the container at application startup time. The framework providing an
implementation of the ServletContainerInitializer MUST bundle in the
META-INF/services directory of the jar file a file called
javax.servlet.ServletContainerInitializer, as per the jar services API,
that points to the implementation class of the ServletContainerInitializer.
In addition to the ServletContainerInitializer we also have an annotation -
HandlesTypes. The HandlesTypes annotation on the implementation of the
ServletContainerInitializer is used to express interest in classes that may
have annotations (type, method or field level annotations) specified in the value of
the HandlesTypes or if it extends / implements one those classes anywhere in the
class’ super types. The HandlesTypes annotation is applied irrespective of the
setting of metadata-complete.

请注意加粗处的说明:大白话的意思就是,启动外部tomcat时就会创建一个ServletContainerInitializer 的实例,这个实例呢必须在META-INF/services下名为javax.servlet.servletcontainerinitializer的文件。他会检查所有jar包。

启动流程原理

org.springframework:spring-web:5.0.8 为例 打开META-INF/services

javax.servlet.servletcontainerinitializer文件内容如下:

1
org.springframework.web.SpringServletContainerInitializer

从servlet3.0规则来看。外部tomcat启动后会扫描这个文件,并创建SpringServletContainerInitializer实例

SpringServletContainerInitializer 【此类为ServletContainerInitializer子类】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//HandlesTypes 的作用是声明 SpringServletContainerInitializer能处理的类型 并且从 Set<Class<?>> //webAppInitializerClasses中可以获取
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
//webAppInitializerClasses 必须不能为空否则应用启动不了
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
//1、不能是接口 也不能是抽象类而且必须是 WebApplicationInitializer的子类
//WebApplicationInitializer默认的实现全是抽象类。只有我们上面的准备工作内写的
//ServletInitializer 一个类符合这个要求
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
/*(ServletInitializer) 实例化 newInstance() 后加入到列表*/
initializers.add((WebApplicationInitializer) ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
//省略代码....
//省略代码....
}
}
}
for (WebApplicationInitializer initializer : initializers) {
//调用onStartup
//ServletInitializer 只有configure 方法,但他是SpringBootServletInitializer的子类
//也就是调用的是SpringBootServletInitializer
initializer.onStartup(servletContext);
}
}
}
WebApplicationInitializer 【SpringServletContainerInitializer能处理的类型】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**WebApplicationInitializer
AbstractContextLoaderInitializer
AbstractDispatcherHandlerInitializer
AbstractServletHttpHandlerAdapterInitializer
AbstractReactiveWebInitializer
JerseyWebApplicationInitializer
SpringBootServletInitializer

请注意全是抽象类。并没有一个真正的class
而我们使用的是springboot框架,所以我们肯定会
选SpringBootServletInitializer 其他的不做多说
*/
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;

}
SpringBootServletInitializer 【WebApplicationInitializer 的抽象实现】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//省略代码....
//当调用onStartup其实就是要启动应用。那这里是先创建根容器。
//主要流程请看 createRootApplicationContext
WebApplicationContext rootAppContext = createRootApplicationContext(
servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
@Override
public void contextInitialized(ServletContextEvent event) {
}
});
}
//省略代码....
}


//主要操作来了。。。。。创建容器
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
//先是创建一个spring应用构建器
SpringApplicationBuilder builder = createSpringApplicationBuilder();
//指定构建器主类
builder.main(getClass());
//获取现有的根应用上下文
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {

this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
//如果有就先创建父级容器
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
//初始化当前servlet容器
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
//当前类的configure没有做任何处理,但是被他的子类 【ServletInitializer】重写了。所调用的是子类,
//指定应用的启动源。
builder = configure(builder);
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
//用构建器构建出一个spring应用
SpringApplication application = builder.build();
//这里就是在判断有没有指定sources类,或者有没有多个等。。。
if (application.getAllSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(getClass()));
}
if (this.registerErrorPageFilter) {
application.addPrimarySources(
Collections.singleton(ErrorPageFilterConfiguration.class));
}
//调用run方法
return run(application);
}
//上面的run方法调用的就是这个类。看到这里是不是觉得熟悉了。。。
protected WebApplicationContext run(SpringApplication application) {
return (WebApplicationContext) application.run();
}
SpringApplication spring应用的启动入口 无论是外部容器启动还是嵌入式容器启动都必须从此处开始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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;
}