从Servlet和SpringBoot整合理解父子容器 Sp

SpringBoot和Spring-web的容器分析

在这篇开始之前,建议先看从Servlet和Spring-web整合理解父子容器

编码方式实现Web.xml配置

通过上一篇的文章知道了,要实现Servlet和Spring-web融合,就需要在web.xml中主要配置两个东西

  1. ContextLoaderListener
  2. DispatchServlet

配置之后,就可以在Servlet容器启动的时候,创建WebApplicationContext。从而启动Spring容器。下面介绍,如果通过硬编码的方法来启动做融合。

在这之前需要了解一下ServletContainerInitializerjavax.servlet.annotation.HandlesTypes

Servlet提供的方式

1. ServletContainerInitializer和javax.servlet.annotation.HandlesTypes

  1. ServletContainerInitializer是在javax.servlet包下面的,它是一个接口,允许在WebApplication启动的阶段通知。此外这个接口一般都是要利用HandlesTypes注解的。
1
2
3
4
5
6
java复制代码package javax.servlet;
import java.util.Set;
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}
  1. HandlesTypes注解

这注解是和ServletContainerInitializer一块使用的,它表示,value里面声明的类要传递给ServletContainerInitializer来做处理,value表示要探测的类。

1
2
3
4
5
java复制代码@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes {
Class<?>[] value();
}

秘诀就是这个,对于Spring来说,就是继承这个类,在之前的时候做事情就好了。把之前在ContextLoaderListener干的事情,一部分是可以放在这里来干的。

Spring利用ServletContainerInitializer来做处理

SpringServletContainerInitializer

可以看到,它实现了ServletContainerInitializer,并且在@HandlesTypes里写WebApplicationInitializer,这就会导致servlet容器会将所有的WebApplicationInitializer组装成一个set,作为参数传递进来。

可以看到,在这里面会实例化传递进来的WebApplicationInitializer(前提是,这个WebApplicationInitializer的实例不是接口,不是抽象类)

还会排序(只要实现了Order接口),然后就是循环调用WebApplicationInitializer了。

问题

  1. SpringServletContainerInitializer是怎么被加载的?

ServletContainerInitializer理论上来说,是一个SPI。既然是SPI,肯定是有一个写好的全路径的配置文件的。Spring-web是在META-INF/services/javax.servlet.ServletContainerInitializer里面。这才是一个典型的SPI加载的过程。突然想到了Dubbo。
2. SpringServletContainerInitializer和WebApplicationInitializer有啥区别?

SpringServletContainerInitializer负责实例化WebApplicationInitializer。并且将ServletContext传递给WebApplicationInitializer。

WebApplicationInitializer是自定义ServletContext的。

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
java复制代码@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<>();

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.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}

if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}

WebApplicationInitializer

这个接口是通过编程的方式来配置ServletContext,和之前的web.xml的功能基本差不多,但这里是通过编程方式来做的。这个接口的实现类会自动被SpringServletContainerInitializer被识别并且实例化。

话说回来,Spring能通过编程的方式来配置,本质上还是人家ServletContext支持这种方式,要是没有对应的api,肯定是不行的。

这个方法本身也很简单,将servletContext传递过来,子类就可以自己做操作了

1
2
3
4
5
java复制代码public interface WebApplicationInitializer {

void onStartup(ServletContext servletContext) throws ServletException;

}

先看看类图

图片.png

作为Spring来说,怎么可能就提供一个简简单单的接口呢?肯定是一堆实现类。下面具体看看他们的操作

AbstractContextLoaderInitializer

在这里面主要干了下面的几件事情

  1. 给ServletContext创建rootAppContext。
  2. 将创建好的rootAppContext传递过去,创建ContextLoaderListener。
  3. 给ContextLoaderListener设置ApplicationContextInitializer。
  4. 给servletContext添加listener。

ContextLoaderListener熟悉把。之前配置在web.xml中的listener。现在编码方式搞进去。并且它的这个构造方法也有有考究的。还记得这个吗?

在这里插入图片描述

在看看他的才有参数的构造函数,这都有是考究的。这里直接设置进去就不需要在ContextLoaderListener创建rootWebApplicationContext。

并且这是抽象类,使用模板设计方法。将创建rootApplicationContext和获取ApplicationContextInitializer的操作留给子类。

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
java复制代码public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
protected final Log logger = LogFactory.getLog(getClass());

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
// 创建rootApplication,并且创建ContextLoaderListener,通过ApplicationContextInitializer来自定义,并且给servletContext添加listener
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}

@Nullable
protected abstract WebApplicationContext createRootApplicationContext();

@Nullable
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}

}
AbstractDispatcherServletInitializer

看这个名字就知道了,给ServletContext配置了一个Servlet,(话说,Spring的起名字确实有一手)。

主要看了下面的几个事情

  1. 调用父类的onStartup方法。
  2. 开始给ServletContext注册DispatchServlet
1. 确定Servlet的名字(重写getServletName方法,可以改变servlet的名字)。
2. 创建webApplicationContext。
3. 创建DispatchServlet(createDispatcherServlet)。
4. 获取ApplicationContextInitializer(getServletApplicationContextInitializers)
5. 给DispatchServlet配置ApplicationContextInitializer。
6. 将servlet添加给ServletContext。
7. 设置刚刚添加进去的Servlet(通过ServletRegistration.Dynamic,默认支持servlet异步)
8. 获取Filter(getServletFilters)
9. 给Servlet添加Filter,并且解决重名问题。(利用循环解决重名问题,最多100),配置Filter
10. 留给子类拓展方法,主要是修改DispatchServlet在ServletContext中的属性,对比web.xml中`servlet`标签里面的配置(customizeRegistration)。

问题

1. 上面说的解决重名的问题,100是啥意思?


在调用servletContext添加filter的时候,如果重名,方法的返回值就是空,如果是空,就走到循环里面,下次的名字就是



1
2
3
> 	java复制代码registration = servletContext.addFilter(filterName + "#" + counter, filter);
>
>
counter会一直到100。如果到了100,就直接报错,同一个filter添加了100次。。也就是说,他允许添加一个filter的时候重名100个对象。
2. 重名的问题,在添加Servlet的时候会有吗?


会。如果重名就直接报错,他可没有处理的操作。
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
java复制代码
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {


public static final String DEFAULT_SERVLET_NAME = "dispatcher";


@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}


protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");

WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name.");
}

registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());

Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}

customizeRegistration(registration);
}


protected String getServletName() {
return DEFAULT_SERVLET_NAME;
}

// 给servlet创建ApplicationContext
protected abstract WebApplicationContext createServletApplicationContext();

// 创建dispatchServlet。
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}

// 获取ApplicationContextInitializer,这是专门给ServletApplicationContext中的
@Nullable
protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
return null;
}

// 获取ServletMapping。这类比的就是<servlet-mapping>
protected abstract String[] getServletMappings();


@Nullable
protected Filter[] getServletFilters() {
return null;
}
// 注册filter
protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
String filterName = Conventions.getVariableName(filter);
Dynamic registration = servletContext.addFilter(filterName, filter);

if (registration == null) {
int counter = 0;

// 处理重名
while (registration == null) {
if (counter == 100) {
throw new IllegalStateException("Failed to register filter with name '" + filterName + "'. " +
"Check if there is another filter registered under the same name.");
}
registration = servletContext.addFilter(filterName + "#" + counter, filter);
counter++;
}
}

registration.setAsyncSupported(isAsyncSupported());
registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
return registration;
}

private EnumSet<DispatcherType> getDispatcherTypes() {
return (isAsyncSupported() ?
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
}


protected boolean isAsyncSupported() {
return true;
}

protected void customizeRegistration(ServletRegistration.Dynamic registration) {
}
}
AbstractAnnotationConfigDispatcherServletInitializer

这个类,更加的具体,创建具体的WebApplicationContext。重写了父类的createRootApplicationContextcreateServletApplicationContext。提供了两个方法,指定配置类。(getRootConfigClasses,getServletConfigClasses)这样就可以利用配置类来启动了。

但是得注意,这里只是调用的context.register()方法,要知道,这个方法之后是必要要调用refresh方法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
java复制代码public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {


@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
else {
return null;
}
}


@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}

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


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

okk。到这里编码方式实现Web.xml配置就结束了,可能会觉得突然断片了,其实后面的内容和从Servlet和Spring-web整合理解父子容器 ,是一样的逻辑。无非就是少了一步创建的过程。别的配置逻辑都是一样的。

上面的流程,结合从Servlet和Spring-web整合理解父子容器 ,画了一张图便于理解。

Tomacat和Spring启动的过程图示

Springboot融合方式

有了上面的了解,Springboot就比较好理解了,因为Springboot有嵌入式的Server。所以Springboot的融合方式和上面的不一样。

按照上面的写法,干线分为两个部分

  1. 创建tomcat的时候创建ServletContainerInitializer做初始化rootwebApplication。
  2. DispatchServlet的webApplication初始化。

1. TomcatStarter

TomcatStarter实现了ServletContainerInitializer借口,这个接口的作用上面已经说了,但是需要注意这个类没有使用HandlesTypes注解,所以,onStartup方法里面第一个参数就是一个空参数。在这个里面主要是 触发ServletContextInitializer.

下面看TomcatStarter是在哪里创建的,并且在Tomcat启动的时候ServletContextInitializer有用的类有哪些,都有什么作用?

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
java复制代码class TomcatStarter implements ServletContainerInitializer {

private static final Log logger = LogFactory.getLog(TomcatStarter.class);

private final ServletContextInitializer[] initializers;

private volatile Exception startUpException;

TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}
// 添加进来,tomcat启动得时候,就会调用,在Spring的编程配置web.xml的时候,会使用@TypeHandle的注解,但是这里不需要
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
catch (Exception ex) {
this.startUpException = ex;
/
if (logger.isErrorEnabled()) {
logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
+ ex.getMessage());
}
}
}
Exception getStartUpException() {
return this.startUpException;
}
}

2. ServletContextInitializer在Springboot启动时候的作用

既然ServletContextInitializer是给Tomcat使用的,并且Springboot的Tomcat是嵌入式的,所以就从创建Tomcat的时候看,肯定有添加的操作。

ServletContextInitializer起作用的地方

ServletWebServerApplicationContext#onRefresh方法

这个方法是在Refresh方法里面定义的模板方法,留给子类拓展。ConfigurableApplicationContext接口中定义Refresh方法,并且在AbstractApplicationContext里面定义了refresh模板方法。

可以看到,在onRefresh调用了createWebServer方法,创建了一个webServer。

1
2
3
4
5
6
7
8
9
10
java复制代码	@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
创建webServer

简单的分析了一下,创建webServer不是这一篇文章的主题,之后专门出一篇,这里就简单的分析分析createWebServer代码逻辑,下面主要干了几点事情:

  1. 如果有ServletContext,直接调用ServletContextInitializer,这说明什么事情,Tomcat已经创建好了。
  2. 如果没有,先获取ServletWebServerFactory,调用factory.getWebServer方法,传递ServletContextInitializer集合。获取webServer
  3. 将webServer包装成WebServerGracefulShutdownLifecycle,WebServerStartStopLifecycle,并且将它注册到beanFactory中。
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
java复制代码	private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
// 替换值,这都是小问题。
initPropertySources();
}

从上面有一个关键点(getSelfInitializer方法),这方法返回值就是一个ServletContextInitializer,那这就是我们的关注点。

有一个题外话,如果不清楚Spring的Refresh方法,有一个简单的方法,看日志看日志,然后搜,比如看下面这种日志:

图片.png

从这个日志就可以知道,这里已经初始化好了webApplicationContext,所以,从这里看准没有错,然后就断点一点点的看。就ok了(一点点的小小的经验)


我们还是顺着上面的方法看下去,getSelfInitializer已经传递给factory.getWebServer方法了,下来的就简单了,看哪些方法传递了给他。

图片.png

看看springboot支持哪些的嵌入式的服务器。

这里主要看TomcatServletWebServerFactory

TomcatServletWebServerFactory#getWebServer(ServletContextInitializer的地方)

在这里面会真正的创建webServer,这篇博客不是分析webServer的创建过程。这里主要看ServletContextInitializer引用的地方。一直看,就会看到下面的代码

可以看到,这里添加了两个

  1. 1
    java复制代码(servletContext) -> this.initParameters.forEach(servletContext::setInitParameter) // 设置init参数
  2. 1
    java复制代码new SessionConfiguringInitializer(this.session) // 配置Session
1
2
3
4
5
6
7
8
9
java复制代码	protected final ServletContextInitializer[] mergeInitializers(ServletContextInitializer... initializers) {
// 这里添加了两个
List<ServletContextInitializer> mergedInitializers = new ArrayList<>(); ServletContextInitializer的实现类,
mergedInitializers.add((servletContext) -> this.initParameters.forEach(servletContext::setInitParameter));
mergedInitializers.add(new SessionConfiguringInitializer(this.session));
mergedInitializers.addAll(Arrays.asList(initializers));
mergedInitializers.addAll(this.initializers);
return mergedInitializers.toArray(new ServletContextInitializer[0]);
}

上面的方法返回的 ServletContextInitializer 数字,将会在TomcatServletWebServerFactory#configureContext里面,创建TomcatStarter,将ServletContextInitializer数字作为参数传递过去。

总结:

到此,知道了在整个Springboot启动的时候,先是会有三个ServletContextInitializer发挥作用。他们是。在创建WebApplicationContext之后,还会获取别的ServletWebContextInitializer。来进行具体的配置化。

  1. ServletWebServerApplicationContext#selfInitialize方法,这就只有一个(这才是重点)
  2. AbstractServletWebServerFactory#mergeInitializers方法,这里面配置了两个(一个是配置session的,一个是配置init-param参数的)

给ServletContext创建WebApplication

上面已经说了,直接看selfInitialize方法,主要干了下面的几个事情

  1. 给ServletContext设置rootWebApplication,为当前的ApplicationContext。给当前的ApplicationContext设置ServletContext(prepareWebApplicationContext)
  2. 将ServletContext包装成ServletContextScope,注册到BeanFactory中,并且添加到servletContext的属性中。(ServletContextScope就是为了包装了ServletContext,为了方便的访问和获取值)
  3. 给bean工厂中注册servletContext(bean名字是ServletContext),ServletConfig(bean名字是servletConfig),parameterMap,这是ServletContext的参数,(这就是一个mapString:String,bean的名字是contextParameters),attributeMap,这是Servlet的属性,(这也是一个Map String:Object,bean的名字是contextAttributes)
  4. 遍历所有的ServletContextInitializer。调用onStartup方法将servletContext传递过去,来做定制化的配置。
1
2
3
4
5
6
7
8
9
java复制代码private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext); //给当前的servletCOntext设置rootWebApplication,将当前的applicationContext设置,并且也设置ServletContext
registerApplicationScope(servletContext); // 这个简单了,创建一个ServletContextScope,然后讲他添加到servletContext和applicationContext中
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// 这里会创建dispatchServlet了 lcnote 这里得注意
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}

到第四步的时候,已经给ServletContext创建好了WebApplicationContext了,重点是getServletContextInitializerBeans方法,主要的作用,

getServletContextInitializerBeans分析

ServletContextInitializerBeans继承了AbstractCollection,表示,他是一个集合。集合里面存放的是ServletContextInitializer。所以,上面的方法里面才可以利用forEarch来遍历。

不是说继承了AbstractCollection,什么不干,就可以利用forEarch遍历了,肯定还有操作,下面先看这个。

主要是重写了迭代器方法,迭代器返回的是sortedList的。sortedList是这里面用来存放数据的地方,在构造函数里面已经设置好了。所以就可以利用forearch了。

1
2
3
4
5
6
7
8
9
10
java复制代码// 主要是重写了下面的方法
@Override
public Iterator<ServletContextInitializer> iterator() {
return this.sortedList.iterator();
}

@Override
public int size() {
return this.sortedList.size();
}

下面具体看看这里面干的事情

  1. 初始化initializers。
  2. 确认initializerTypes,默认是ServletContextInitializer。之后会通过这个类型会从beanFactory中获取对应的bean。(要知道从beanFactory中获取bean的操作会走一边获取bean的过程,自动注入也是在这里发生的)
  3. 根据initializerTypes的值去beanFactory中获取值,将这些bean添加到initializers(bean的集合)里面,并且会获取到这些特殊的bean处理的Resource,添加到seen里面。
  4. 利用适配器,对系统中还存在的Filter和Servlet做适配器。并且也会添加到initializers和seen
  5. 排序,赋值给sortedList。(想想之前的迭代器。就是利用这个来做处理的)
  6. 简单的打印日志
1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码	public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
this.initializers = new LinkedMultiValueMap<>();
this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
: Collections.singletonList(ServletContextInitializer.class);
addServletContextInitializerBeans(beanFactory);
addAdaptableBeans(beanFactory);
List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
.collect(Collectors.toList());
this.sortedList = Collections.unmodifiableList(sortedInitializers);
logMappings(this.initializers);
}
ServletContextInitializerBeans#addServletContextInitializerBeans

从这个方法里面会从beanFactory中获取值(要知道,从beanFactory中获取bean,要是没有的话,就直接会创建,并且在这个途中会应用到所有的BeanPostProcess,自动注入)

1
2
3
4
5
6
7
8
java复制代码private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,
initializerType)) {
addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
}
}
}
ServletContextInitializerBeans#addServletContextInitializerBean

上一步从beanFactory中获取到bean,然后通过不同的类型做处理。注意看addServletContextInitializerBean的source参数。source是ServletRegistrationBean里面的衍生物,下面的这几个,只是参数的类型不同,都是调用了addServletContextInitializerBean方法。将值添加到initializers和seen里面。

问题:

  1. 上面一直提到的seen和initializers是什么?

initializers是一个数组。

定义如下:

private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;

这里面保存的是key是class对象,value是这个class关联的ServletContextInitializer链表。到最后的时候,会把value的值组成一个list,赋值给sortedList

seen是一个Set

定义如下:

private final Set seen = new HashSet<>();

这个Set会在addServletContextInitializerBean里面添加值,这里面添加的值是ServletRegistrationBean或者FilterRegistrationBean或者DelegatingFilterProxyRegistrationBean或者ServletListenerRegistrationBean调用各自的方法获取的值,可以理解为这些的衍生物。所以是一个Set。

  • FilterRegistrationBean和ServletRegistrationBean和DelegatingFilterProxyRegistrationBean和ServletListenerRegistrationBean都是一些什么东西

详情在下面看。

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
> > java复制代码	private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,
> > ListableBeanFactory beanFactory) {
> > if (initializer instanceof ServletRegistrationBean) {
> > Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
> > addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
> > }
> > else if (initializer instanceof FilterRegistrationBean) {
> > Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
> > addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
> > }
> > else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
> > String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();
> > addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
> > }
> > else if (initializer instanceof ServletListenerRegistrationBean) {
> > EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener();
> > addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
> > }
> > else {
> > addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory,
> > initializer);
> > }
> > }
> >
> >
RegistrationBean和它的实现类分析

图片.png

从上面的图可以很清楚的看到,他是实现了ServletContextInitializer接口,ServletContextInitializer。在创建WebApplicationContext的时候已经创建了三个了,这三个都交给TomcatStarter来循环调用了,所以,这里的这些实现类都是在容器里面注入的,和之前的是没有关系的,但是他们的功能都是一样,在ServletContext启动的时候配置东西。

既然实现了ServletContextInitializer接口,直接先看onStartup方法

1. RegistrationBean#onStartup方法

一个简单的模板方法,先获取description,在判断this是否启动,没有就直接打日志,有的话就调用register方法。

getDescription,isEnabled,register这都是留给子类来实现的。

1
2
3
4
5
6
7
8
9
10
11
12
> > java复制代码	@Override
> > public final void onStartup(ServletContext servletContext) throws ServletException {
> >
> > String description = getDescription();
> > if (!isEnabled()) {
> > logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
> > return;
> > }
> > register(description, servletContext);
> > }
> >
> >

继续按照上面的图来看,

2. DynamicRegistrationBean

在之前的基础上,增加了配置Registration.Dynamic的能力,Registration.Dynamic在之前介绍过,就是给ServletContext添加Servlet或者Filter之后的返回值,还记得上面说的 100吗?

这里就在之前的基础上,增加了对配置到ServletContext中的Servlet和Filter做配置。

直接看它是怎么写

好家伙,写的还挺骚的,可以看到,处理继承RegistrationBean,还增加了范型支持,这里面处理的值是继承于Registration.Dynamic的。并且增加了一些基础的配置,比如asyncSupported,初始参数。

既然继承了RegistrationBean,直接看它重写的方法,register

在register里面又增加了模板方法。addRegistration和configure,先调用addRegistration将servletContext传递过去,作为子类来说,就可以做一些额外的配置了, 比如对于ServletRegistrationBean来说,就可以在这个里面添加DispatchServlet,在添加完了之后,ServletContext就可以返回一个Registration.Dynamic的子类,传递给configure,就可以做自定义的配置了,也可以重写这个方法,比如对于ServletRegistrationBean来说,配置init参数,添加Servlet能够处理的Mapping等扽信息。

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
> > java复制代码public abstract class DynamicRegistrationBean<D extends Registration.Dynamic> extends RegistrationBean {
> >
> > private static final Log logger = LogFactory.getLog(RegistrationBean.class);
> >
> > private String name;
> >
> > private boolean asyncSupported = true;
> >
> > private Map<String, String> initParameters = new LinkedHashMap<>();
> >
> > public void setName(String name) {
> > Assert.hasLength(name, "Name must not be empty");
> > this.name = name;
> > }
> >
> > public void setAsyncSupported(boolean asyncSupported) {
> > this.asyncSupported = asyncSupported;
> > }
> >
> > public boolean isAsyncSupported() {
> > return this.asyncSupported;
> > }
> >
> >
> > public void setInitParameters(Map<String, String> initParameters) {
> > Assert.notNull(initParameters, "InitParameters must not be null");
> > this.initParameters = new LinkedHashMap<>(initParameters);
> > }
> >
> > public Map<String, String> getInitParameters() {
> > return this.initParameters;
> > }
> >
> >
> > public void addInitParameter(String name, String value) {
> > Assert.notNull(name, "Name must not be null");
> > this.initParameters.put(name, value);
> > }
> >
> > @Override
> > protected final void register(String description, ServletContext servletContext) {
> > D registration = addRegistration(description, servletContext);
> > if (registration == null) {
> > logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
> > return;
> > }
> > configure(registration);
> > }
> >
> > protected abstract D addRegistration(String description, ServletContext servletContext);
> >
> > protected void configure(D registration) {
> > registration.setAsyncSupported(this.asyncSupported);
> > if (!this.initParameters.isEmpty()) {
> > registration.setInitParameters(this.initParameters);
> > }
> > }
> >
> >
> > protected final String getOrDeduceName(Object value) {
> > return (this.name != null) ? this.name : Conventions.getVariableName(value);
> > }
> > }
> >
> >

就先分析到这里把,子类的实现可以自己去看看,已经很清楚了,现在还有一个问题,这些DynamicRegistrationBean是在哪里注入的?

一开始我也不知道,说说我是怎么做的。比如DispatcherServletRegistrationBean吧。

先点开他,然后看它的引用,然后打new, 肯定是要创建的,基本上就搞定了。

图片.png

这一看就是下面的两个,上面的是端点

图片.png

在DispatcherServletAutoConfiguration里面,可以看到这个bean是需要一个dispatchServlet的,那这个DispatchServlet是在哪里注入的呢?

图片.png

就在这个方法的上面,可以看到,直接new出了dispatchServlet,要清楚,这里的DisaptchServlet是添加到Bean容器里面的。所以,它也走bean的生命周期那一套。

addAdaptableBeans分析

显示从beanFactory中获取MultipartConfigElement,如果有多个,只获取一个。

重点就是下面的这几个方法了

1. addAsRegistrationBean方法
2. ServletRegistrationBeanAdapter类和FilterRegistrationBeanAdapter和ServletListenerRegistrationBeanAdapter

问题:

1. addAdaptableBeans的目的是为了什么?
我觉得是为了将applicationContext中的配置的Servlet和Filter添加到ServletContext中,并且容易配置。也就是说在Springboot中也是可以配置多个Servlet的。
1
2
3
4
5
6
7
8
9
10
11
> > java复制代码	protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
> > MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
> > addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
> > addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
> > for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
> > addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
> > new ServletListenerRegistrationBeanAdapter());
> > }
> > }
> >
> >
addAsRegistrationBean分析

从bean工厂中获取beanType类型的bean,这些bean没有在seen集合里面。得到一个list,遍历这list,调用传递进来的适配器,适配,给适配器设置原来bean的order,添加到seen和initializers里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
> > java复制代码private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
> > Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
> > List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
> > for (Entry<String, B> entry : entries) {
> > String beanName = entry.getKey();
> > B bean = entry.getValue();
> > if (this.seen.add(bean)) {
> > // One that we haven't already seen
> > RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
> > int order = getOrder(bean);
> > registration.setOrder(order);
> > this.initializers.add(type, registration);
> > if (logger.isTraceEnabled()) {
> > logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
> > + order + ", resource=" + getResourceDescription(beanName, beanFactory));
> > }
> > }
> > }
> > }
> >
> >

下面的这三个都实现了RegistrationBeanAdapter接口,这个接口表示是可以将给定的bean转为RegistrationBean,这个RegistrationBean意味着在ServletContext中可以添加的类。重点看它的createRegistrationBean方法

ServletRegistrationBeanAdapter

就是将source转化为ServletRegistrationBean。要是再配置类里面注入的,可以手动的设置进去那个范型具体的值,但是这里就只能自己来了。

如果除了seen里面包含的bean之外,只有一个,url就是/,否则就是”/“ + name + “/“。

如果要创建的bean 的名字是DISPATCHER_SERVLET_NAME,就默认/,其余的就是创建ServletRegistrationBean,并且设置multipartConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
> > java复制代码	@Override
> > public RegistrationBean createRegistrationBean(String name, Servlet source, int totalNumberOfSourceBeans) {
> > String url = (totalNumberOfSourceBeans != 1) ? "/" + name + "/" : "/";
> > if (name.equals(DISPATCHER_SERVLET_NAME)) {
> > url = "/"; // always map the main dispatcherServlet to "/"
> > }
> > ServletRegistrationBean<Servlet> bean = new ServletRegistrationBean<>(source, url);
> > bean.setName(name);
> > bean.setMultipartConfig(this.multipartConfig);
> > return bean;
> > }
> >
> >
FilterRegistrationBeanAdapter

这里其实和上面也是一样,不过返回的是FilterRegistrationBean。

1
2
3
4
5
6
7
8
> > java复制代码	@Override
> > public RegistrationBean createRegistrationBean(String name, Filter source, int totalNumberOfSourceBeans) {
> > FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(source);
> > bean.setName(name);
> > return bean;
> > }
> >
> >
ServletListenerRegistrationBeanAdapter

这里也是一样的,返回的是ServletListenerRegistrationBean。

1
2
3
4
5
6
7
> > java复制代码	@Override
> > public RegistrationBean createRegistrationBean(String name, EventListener source,
> > int totalNumberOfSourceBeans) {
> > return new ServletListenerRegistrationBean<>(source);
> > }
> >
> >

到这里就知道了,ServletContextInitializerBeans里面干了什么事情。剩下的就是循环调用了。再结合上面的代码,总结下来就是:

1. 创建ServletContextInitializerBeans。再这里面会加载所有的实现了ServletContextInitializer的类,
2. 其中有一个特殊的bean(RegistrationBean),再这个实现类里面会配置ServletContext。
3. RegistrationBean的实现类是自动配置类注入的。其中就有DispatchServlet。会创建dispatchServlet,并且会创建DispatcherServletRegistrationBean,将DispatchServlet传回去,再调用ServletContextInitializer的onStartup方法的时候会将DispatchServlet添加到ServletContext中,并且做配置。

到现在已经说清楚了,webApplicationContext和ServletContext的关联,DispatchServlet和ServletContext的关联,但是现在还有一个问题,回想到之前说的,DispatchServlet也有一个webApplicationContext,一直到这里也没有看到。

下面就来分析分析

3. DispatchServlet中的applicationContext

再创建dispatchServlet的时候,webApplicationContext已经创建好。DispatchServlet已经添加容器了。也没有看到DispatchServlet设置applicationContext的操作。那它怎么是怎么做的?

图片.png

dispatchServlet实现了ApplicationContextAware接口,通过这个接口就会将applicationContext传递过来,dispatchServlet创建的时候走了完整的过程,所以,这个applicationContext就是之前的Context。也就是和RootWebApplication一样。

举个例子看看

在之前的spring-web里面,有两个容器,子可以访问父,父不能访问子。也就是controller不能注入到service中,但是在Springboot中是可以的。

图片.png
到此,从Servlet和SpringBoot整合理解父子容器就结束了。

关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%