前言

上篇我们着重讲了SpringMVC与Servlet关系,而且SpringMVC通过web.xml配置设置DispatcherServlet启动,同时启动webApplicationContext容器,然后通过refresh()方法,把所有Spring相关的bean注册和启动,这里包括controller,Interceptor,viewResolver,view等等组件。

在上篇文章的结尾我说了另外一种另外的一种启动webApplicationContext容器的方法。这是Servlet3.0协议引进的ServletContainerInitializer。下面我就讲讲ServletContainerInitializer具体作用和它与SpringBoot关系。SpringBoot主要解决了Spring的配置,有一个思想叫约定大于配置。而这种约定大于配置,或者使有默认的以来的SpringBean的注入都是依赖于SPI。关于SPI我之前在自己动手写starter的文章中有提及,想要看的可以去翻翻。

因为SpringMVC基本都已经过时了,因为现在开发中不仅仅是Web端,还有手机端,微信公众号,小程序等等。同时H5技术迅猛的发展,在性能需求和业务演化越来越复杂,前后端逐渐的分离,越来越多的服务的接口都是Rest的API(SpringBoot也支持Web模板),不再像之前提供API和View。而且SpringBoot也让Spring越来越少的配置,也然Web程序简单很多。所以我们下面就讲讲SpringBoot启动关键ServletContainerInitializer。讲这个之前我们先了解一些关于Tomcat的组成会让我们更加了解这个过程。

本篇博客都是基于Tomcat实现的Servlet标准去实现的,里面会涉及一部分Tomcat的源码和Tomcat的概念,目前讲的都是外置Tomcat运行。

Tomcat的介绍

Tomcat是JavaWeb容器,简单说就是一个可以运行java程序的web服务器,它主要实现的就是Java的Servlet标准。同样实现这套标准的还有jetty、jboss 、weblogic等等。不过我们这里主要讲的还是最常见的Tomcat

Tomcat的组成

Tomcat主要由Container 和 Connector 以及相关组件构成。(我会根据一个URL来讲下这几部分http://localhost:8080/demo/hello/world)

Server:指的就是整个 Tomcat 服 务器,包含多组服务,负责管理和 启动各个 Service,同时监听 8005 端口发过来的 shutdown 命令,用 于关闭整个容器 ;(一个Tomcat就是一个server,localhost:8080)

Service:Tomcat 封装的、对外提 供完整的、基于组件的 web 服务, 包含 Connectors、Container 两个 核心组件,以及多个功能组件,各 个 Service 之间是独立的,但是共享 同一 JVM 的资源 ;(通俗来讲就是一个工程,在传统的eclipse里面,一个tomcat可以部署多个工程 ,/demo/)

Connector:Tomcat 与外部世界的连接器,监听固定端口接收外部请求,传递给 Container,并 将 Container 处理的结果返回给外部;(这个一般电话多线程去控制的,用于处理网络)

Container:Catalina,Servlet 容器,内部有多层容器组成,用于管理 Servlet 生命周期,调用 servlet 相关方法。(对应可能就是一个controller概念,主要是处理业务,/hello/)

Loader:封装了 Java ClassLoader,用于 Container 加载类文件; Realm:Tomcat 中为 web 应用程序提供访问认证和角色管理的机制;

Container组成

Engine:Servlet 的顶层容器,包含一 个或多个 Host 子容器; Host:虚拟主机,负责 web 应用的部 署和 Context 的创建; Context:Web 应用上下文,包含多个 Wrapper,负责 web 配置的解析、管 理所有的 Web 资源; Wrapper:最底层的容器,是对 Servlet 的封装,负责 Servlet 实例的创 建、执行和销毁。

生命周期管理

Tomcat 为了方便管理组件和容器的生命周期,定义了从创建、启动、到停止、销毁共 12 中状态,tomcat 生命周期管理了内部状态变化的规则控制,组件和容器只需实现相应的生命周期 方法即可完成各生命周期内的操作(initInternal、startInternal、stopInternal、 destroyInternal);关于ServletContainerInitializer的扫描和启动主要是startInternal开始的。

Tomcat 的生命周期管理引入了事件机制,在组件或容器的生命周期状态发生变化时会通 知事件监听器,监听器通过判断事件的类型来进行相应的操作。 事件监听器的添加可以在 server.xml 文件中进行配置;

Tomcat 各类容器的配置过程就是通过添加 listener 的方式来进行的,从而达到配置逻辑与 容器的解耦。如 EngineConfig、HostConfig、ContextConfig。 EngineConfig:主要打印启动和停止日志 HostConfig:主要处理部署应用,解析应用 META-INF/context.xml 并创建应用的 Context ContextConfig:主要解析并合并 web.xml,扫描应用的各类 web 资源 (filter、servlet、listener)

listener的类图

Tomcat处理请求的过程

1、根据 server.xml 配置的指定的 connector 以及端口监听 http、或者 ajp 请求。

2、请求到来时建立连接,解析请求参数,创建 Request 和 Response 对象,调用顶层容器 pipeline 的 invoke 方法。

3、容器之间层层调用,最终调用业务 servlet 的 service 方法。

4、Connector 将 response 的数据写到 socket 中。

上述就是Tomcat的大体工作原理和过程。下面我们主要讲ServletContainerInitializer这个容器初始化接口,感觉明明是说SpringMVC的,怎么感觉和SpringMVC没什么关系了。

ServletContainerInitializer介绍

ServletContainerInitializer源码

ServletContainerInitializer:Servlet 3.0引入的接口,用于在web应用启动时动态添加servletfilterlistener;基于spi机制,META-INF/services/javax.servlet.ServletContainerInitializer文件中存放实现该接口的类,这些类会被容器调用;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package javax.servlet;

import java.util.Set;

/**
* Interface which allows a library/runtime to be notified of a web
* application's startup phase and perform any required programmatic
* registration of servlets, filters, and listeners in response to it.
* covered must follow the
* application's classloading delegation model.
*
* @see javax.servlet.annotation.HandlesTypes
*
* @since Servlet 3.0
*/
public interface ServletContainerInitializer {

public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}

上面是ServletContainerInitializer的接口声明,我把大部分的注释都去掉了。我们能出来他是Servlet3.0协议开始有的。这里有个注解@HandlesTypes我后面解释,这地方先挖个坑。

SpringServletContainerInitializer源码

了解SPI的人都知道,SPI是通过一个接口去指定一系列方法抽象。具体的实现框架或者实现类去把抽象方法实现。然后在用到这个SPI方法时候会把根据配置文件去调用这个类的实现方法,有点钩子的赶脚(hook)本质还是解耦(Java SPI是一次性都加载)。通过实现类去实现对应功能,我们上面已经看了抽象,下面我们就看看与之相关的实现。

在Tomcat加载Spring的jiar时候会把jiar包中META-INF/services/javax.servlet.ServletContainerInitializer文件加载,这里面就包含了ServletContainerInitializer的实现类org.springframework.web.SpringServletContainerInitializer

我们就分析分析这个org.springframework.web.SpringServletContainerInitializer这个类。这个类是SpringBoot启动关键,通过这个启动可以启动和创建SpringBoot的webApplicationContext对象。这样才能让整个基于SpringBoot的启动成为可能。下面No BB Show Code

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
package org.springframework.web;

import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;

import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

//实现的ServletContainerInitializer的onStartup方法,方法主要将WebApplicationInitializer的相关类的对象都实例化,这里使用反射方法实现实例化。
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
//获取WebApplicationInitializer这个列表是怎么来的,是通过@HandlesTypes注解实现的,这个注解实现在web容器实现(Tomcat)
List<WebApplicationInitializer> initializers = new LinkedList<>();

if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.(waiClass)) {
try {
//将Set中的Class放入可以实例化的initializers列表,这个里面做了一些过滤,过滤抽象类,接口类。同时将WebApplicationInitializer类实例化。后面我们介绍那些类实现WebApplicationInitializer
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
//如果WebApplicationInitializer实例化的列表是控,说明这个目录没有WebApplicationInitializer
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
//日志打印
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
//将WebApplicationInitializer实例化的对象排序
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
//按照拍好顺序调用onStartup()方法,去初始化SpringBoot
initializer.onStartup(servletContext);
}
}

}

上面我们能看出来,这个SpringServletContainerInitializer类就是把所有WebApplicationInitializer的类实例化,同时调用onStartup()方法.我们看到@HandlesTypes(WebApplicationInitializer.class)这行代码,这也就是他能获取所有WebApplicationInitializer类的列表。下面我们看下@HandlesTypes注解使用的地方,org.apache.catalina.startup.ContextConfig类,他是Tomcat启动的类型,它是获取Context配置的类。

HandlesTypes的处理

处理@HandlesTypes注解的类。这个方法名刚好也说明它处理的事情,初始化服务容器,扫描jar包中服务容器实现类。

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
62
63
64
65
66
/**
* Scan JARs for ServletContainerInitializer implementations.
*/
protected void processServletContainerInitializers() {

List<ServletContainerInitializer> detectedScis;
try {
//获取Web服务的类加载器
WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
//加载所有的ServletContainerInitializer的类
detectedScis = loader.load(ServletContainerInitializer.class);
} catch (IOException e) {//异常后返回
log.error(sm.getString(
"contextConfig.servletContainerInitializerFail",
context.getName()),
e);
ok = false;
return;
}
//初始化ServletContainerInitializer类的实现
for (ServletContainerInitializer sci : detectedScis) {
//将ServletContainerInitializer的Map中的结合Class
initializerClassMap.put(sci, new HashSet<Class<?>>());

HandlesTypes ht;
try {
//获取HandlesTypes(SpringServletContainerInitializer的处理类是WebApplicationInitializer.class)
ht = sci.getClass().getAnnotation(HandlesTypes.class);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.info(sm.getString("contextConfig.sci.debug",
sci.getClass().getName()),
e);
} else {
log.info(sm.getString("contextConfig.sci.info",
sci.getClass().getName()));
}
continue;
}
if (ht == null) {
continue;
}
//在SpringServletContainerInitializer中是WebApplicationInitializer.class
Class<?>[] types = ht.value();
if (types == null) {
continue;
}

for (Class<?> type : types) {
if (type.isAnnotation()) {
handlesTypesAnnotations = true;
} else {
handlesTypesNonAnnotations = true;
}
//构建WebApplicationInitializer的set
Set<ServletContainerInitializer> scis =
typeInitializerMap.get(type);
if (scis == null) {
scis = new HashSet<>();
//装入Map
typeInitializerMap.put(type, scis);
}
scis.add(sci);
}
}
}

ServletContainerInitializer的启动

上面我们看到Tomcat如何处理对应的ServletContainerInitializer要处理的HandlesTypes的类。剩下就是ServletContainerInitializer的startUp方法是在什么时候启动。下面是StandContext.class代码段的截取

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
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
..........
//省略好多代码
// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
l ok = false;
break;
}
}
//省略好多代码
...........
}
//从这段代码能看出来ServletContainerInitializer是放到一个Map里然后循环去启动的。启动的时候还塞了HandlesTypes注解要处理的使用类,关于这个Map怎么产生的和处理的我就没仔细研究了。

说了那么多Tomcat和ServletContainerInitializer相关的,终于可以说说SpringBoot的启动关键WebApplicationInitializer类。

WebApplicationInitializer介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.springframework.web;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
public interface WebApplicationInitializer {

/**
* Configure the given {@link ServletContext} with any servlets, filters, listeners
* context-params and attributes necessary for initializing this web application. See
* examples {@linkplain WebApplicationInitializer above}.
* @param servletContext the {@code ServletContext} to initialize
* @throws ServletException if any call against the given {@code ServletContext}
* throws a {@code ServletException}
*/
void onStartup(ServletContext servletContext) throws ServletException;

}

WebApplicationInitializer主要用于配置 servlets, filters, listeners等,同时为初始化web应用提供必要的参数,简单来讲是通过WebApplicationInitializer来初始化整个web应用,用于代替web.xml。这个我们在这个接口的实现类就能看出来,最经典的也就是SpringBootServletInitializer。SpringBoot的容器和启动都是在这个类开始的,也是通过这个类实现初始化。

SpringBootServletInitializer的代码分析

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package org.springframework.boot.web.servlet.support;

//这里我们能看出来它是一个抽象类,说明只有别人实现的时候才会被调用。所以一般使用外置的Tomcat取启动都会实现这个类。并在里面做配置
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {

protected Log logger; // Don't initialize early

private boolean registerErrorPageFilter = true;

/**
* Set if the {@link ErrorPageFilter} should be registered. Set to {@code false} if
* error page mappings should be handled via the server and not Spring Boot.
* @param registerErrorPageFilter if the {@link ErrorPageFilter} should be registered.
*/
//注册错误页
protected final void setRegisterErrorPageFilter(boolean registerErrorPageFilter) {
this.registerErrorPageFilter = registerErrorPageFilter;
}
//这个地方在SpringServletContainerInitializer时候被调用
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
//创建web应用上下文
WebApplicationContext rootAppContext = createRootApplicationContext(
servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
@Override
public void contextInitialized(ServletContextEvent event) {
// no-op because the application context is already initialized
}
});
}
else {
this.logger.debug("No ContextLoaderListener registered, as "
+ "createRootApplicationContext() did not "
+ "return an application context");
}
}
//创建web应用上下文
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
SpringApplicationBuilder builder = createSpringApplicationBuilder();
builder.main(getClass());
//获取存在的Web应用上下文
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {//如果不为控设置属性,然后创建ParentContextApplicationContextInitializer对象
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
//创建ServletContextApplicationContextInitializer对象
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
//设置ApplicationContext的类属性
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
builder = configure(builder);
//设置一个实现监听器,这个是内部私有类
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
//根据上面属性值创建SpringApplication对象。
SpringApplication application = builder.build();
//检查新创建的SpringApplication对象
if (application.getAllSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(getClass()));
}
Assert.state(!application.getAllSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.addPrimarySources(
Collections.singleton(ErrorPageFilterConfiguration.class));
}
//启动SpringApplication对象
return run(application);
}

/**
* Returns the {@code SpringApplicationBuilder} that is used to configure and create
* the {@link SpringApplication}. The default implementation returns a new
* {@code SpringApplicationBuilder} in its default state.
* @return the {@code SpringApplicationBuilder}.
* @since 1.3.0
*/
//创建SpringApplicationBuilder对象,就是你创建工程的那个starter的对象。
protected SpringApplicationBuilder createSpringApplicationBuilder() {
return new SpringApplicationBuilder();
}

/**
* Called to run a fully configured {@link SpringApplication}.
* @param application the application to run
* @return the {@link }
*/
//启动SpringBoot的run方法,在这个地方SpringBoot启动的点,这个地方就是我们平常自己在Idea中点击run()方法的地方。我们自己创建的所有的启动应用都是基于SpringApplication去启动的。这里面去去创建Bean容器,实例化Bean.
protected WebApplicationContext run(SpringApplication application) {
return (WebApplicationContext) application.run();
}
//获取存在的Spring 的容器
private ApplicationContext getExistingRootWebApplicationContext(
ServletContext servletContext) {
Object context = servletContext.getAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (context instanceof ApplicationContext) {
return (ApplicationContext) context;
}
return null;
}

/**
* Configure the application. Normally all you would need to do is to add sources
* (e.g. config classes) because other settings have sensible defaults. You might
* choose (for instance) to add default command line arguments, or set an active
* Spring profile.
* @param builder a builder for the application context
* @return the application builder
* @see SpringApplicationBuilder
*/
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder;
}
//内部类web环境注册初始化
private static final class WebEnvironmentPropertySourceInitializer
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

private final ServletContext servletContext;

private WebEnvironmentPropertySourceInitializer(ServletContext servletContext) {
this.servletContext = servletContext;
}
//通过应用事件把配置放到环境变量取
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (environment instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) environment)
.initPropertySources(this.servletContext, null);
}

}
//获取排序,这个是最高的,最做处理。
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}

}

}

从上面代码,我们能看出来这个是通过SpringApplicationBuilder对象去创建SpringApplication(就是我们平常说的starter对象)。然后调用SpringBootApplication的run方法。Spring这个是一个建造器模式,在涉及复杂对象的时候会使用,如果你对建造起模式理解不够深,你看下这个SpringApplicationBuilder的实现。

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan(basePackages= "com.qjq")
@EnableTransactionManagement
public class JustbootApplication {
//等同上面的SpringBootServletInitializer的run方法。
public static void main(String[] args) {
SpringApplication.run(JustbootApplication.class, args);
}
}

上面代码就是讲SpringBoot程序启动的类。从这个地方我们能看到SpringApplication.run()方法是个入口。后面的所有操作是在SpringApplication相关类实现的。我这里先埋哥伏笔,等到下次我们讲SpringBoot是如何集成Tomcat变成fatJar启动的时候在说。

SpringMVC中父子容器的补充

SpringMVC是有两个Context,一个是WebApplicationContext,一个是RootWebApplicationContext。Web的上下文更多关注是web层Bean(controller,handlerMapping,viewResoler等)它可以被struts等其他框架去替代了,更多使用spring-servlet.xml。但是Root上下文更多是管理Service层,dao层,包括一些组件的bean注入使用的是applicationContext.xml。然后一般是Root先初始化,然后才是Web初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

//root的配置类
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{SecurityAdminConfig.class, DataSourceConfig.class, JavaConfig.class};
}
//web的配置类
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebMvcConfig.class};
}

在创建时候也会有

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
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {

//创建父容器
@Override
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}
//创建子容器
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
servletAppContext.register(configClasses);
}
return servletAppContext;
}


protected abstract Class<?>[] getRootConfigClasses();


protected abstract Class<?>[] getServletConfigClasses();

如下图所示关系

在启动时候也可以看到日志

尽管说是分两个容器去管理,但是调refresh()方法这个触发点是在子容器内的。

总结

我总结了两篇关于SpringMVC和SpringBoot如何通过Tomcat等web容器去启动。SpringMVC是通过web.xml在启动Servlet时候进行容器初始化。这种方式增加web.xml的配置,同时其好多Bean也可以注入进来。但随着Spring配置越来越复杂化,大家也不想依赖于xml,后面就有了ServletContainerInitializer实现。它的目的就是代替web.xml。同时在Spring内配置也从XML转移到JavaConfig上来。针对SpringMVC还有配置类只要继承WebApplicationInitializer对应实现的抽象类就好了。然后在类里面去写配置,渐渐SpringBoot的starter的出现,使ServletContainerInitializer+JavaSPI使SpringBoot逐渐摆脱xml,实现约定大于配置的整个构造。所以说约定大于配置也不是一下子就出来的,也是有哥过程的。不过这个思想还是借鉴了rails这个框架。Spring这个缝合怪吸收了好多其他框架的优点,也推动着技术的变革。技术没有好坏,只有时候受欢迎,它受欢迎的前提使被大家认可,同时给苦逼的程序员带来生产力的提高。

参考

Tomcat的组成与工作原理

SpringMVC的父子容器