Spring Boot 对于 servlet 的处理才是重头戏:

  • 其一,是因为 Spring Boot 使用范围很广,很少有人用 spring 而不用 Spring Boot 了
  • 其二,是因为它没有完全遵守 Servlet3.0 的规范!

注册方式一:Servlet 3.0 注解 + @ServletComponentScan

Spring Boot 依旧兼容 Servlet 3.0 一系列以 @Web* 开头的注解:@WebServlet,@WebFilter,@WebListener

1
2
3
4
@WebServlet("/hello")
public class HelloWorldServlet extends HttpServlet{}
@WebFilter("/hello/*")
public class HelloWorldFilter implements Filter {}

不要忘记让启动类去扫描到这些注解

1
2
3
4
5
6
7
8
@SpringBootApplication
@ServletComponentScan
public class SpringBootServletApplication {

public static void main(String[] args) {
SpringApplication.run(SpringBootServletApplication.class, args);
}
}

如果真的有特殊需求,需要在 Spring Boot 下注册 servlet,filter,可以采用这样的方式,比较直观。

注册方式二:RegistrationBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public ServletRegistrationBean helloWorldServlet() {
ServletRegistrationBean helloWorldServlet = new ServletRegistrationBean();
myServlet.addUrlMappings("/hello");
myServlet.setServlet(new HelloWorldServlet());
return helloWorldServlet;
}

@Bean
public FilterRegistrationBean helloWorldFilter() {
FilterRegistrationBean helloWorldFilter = new FilterRegistrationBean();
myFilter.addUrlPatterns("/hello/*");
myFilter.setFilter(new HelloWorldFilter());
return helloWorldFilter;
}

ServletRegistrationBean 和 FilterRegistrationBean 都集成自 RegistrationBean ,RegistrationBean 是 Spring Boot 中广泛应用的一个注册类,负责把 servlet,filter,listener 给容器化,使他们被 Spring 托管,并且完成自身对 Web 容器的注册。这种注册方式也值得推崇。
image.png

  • 前三个,帮助容器注册 filter,servlet,listener
  • 最后的 DelegatingFilterProxyRegistrationBean 使用的不多,但熟悉 Spring Security 的朋友不会感到陌生,SpringSecurityFilterChain 就是通过这个代理类来调用的。

SpringBoot 中 Servlet 加载流程的源码分析

Initializer 被替换为 TomcatStarter

当使用内嵌的 Tomcat 时,你会发现 Spring Boot 完全走了另一套初始化流程,完全没有使用前面提到的 SpringServletContainerInitializer,而是进入了 org.springframework.boot.web.embedded.tomcat.TomcatStarter 这个类中
image.png
内嵌 Tomcat 的加载不依赖于 Servlet3.0 规范和 SPI !它完全走了一套独立的逻辑。

我们在使用 Spring Boot 时,开发阶段一般都是使用内嵌 Tomcat 容器,但部署时却存在两种选择:

  • 一种是打成 jar 包,使用 java -jar 的方式运行
  • 另一种是打成 war 包,交给外置容器去运行。

前者就会导致容器搜索算法出现问题,因为这是 jar 包的运行策略,不会按照 Servlet 3.0 的策略去加载 ServletContainerInitializer!

TomcatStarter 中的 ServletContextInitializer 是关键

TomcatStarter 中 org.springframework.boot.context.embedded.ServletContextInitializer[] initializers 属性,是 Spring Boot 初始化 servlet,filter,listener 的关键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class TomcatStarter implements ServletContainerInitializer {

private final ServletContextInitializer[] initializers;

TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}

@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}

}

image.png
initializers 只包含了三个类,其中只有第一个类看上去比较核心,注意第一个类不是 EmbeddedWebApplicationContext !而是这个类中的 $1 匿名类,为了搞清楚 Spring Boot 如何加载 filter、servlet、listener ,看来还得研究下 EmbeddedWebApplicationContext 的结构

EmbeddedWebApplicationContext 中的 6 层迭代加载

第一层:onRefresh()

ApplicationContext 的生命周期方法,EmbeddedWebApplicationContext 的实现非常简单,只干了一件事:

1
2
3
4
5
6
7
8
9
10
@Override
protected void onRefresh() {
super.onRefresh();
try {
createEmbeddedServletContainer(); //第二层的入口
} catch (Throwable ex) {
throw new ApplicationContextException("Unable to start embedded container",
ex);
}
}

调用 #createEmbeddedServletContainer() 方法,连接到了第二层

第二层:createEmbeddedServletContainer()

看名字 Spring 是想创建一个内嵌的 Servlet 容器,ServletContainer 其实就是 servlet、filter、listener 的总称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void createEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
ServletContext localServletContext = getServletContext();
if (localContainer == null && localServletContext == null) {
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
this.embeddedServletContainer = containerFactory
.getEmbeddedServletContainer(getSelfInitializer()); // 第三层的入口
} else if (localServletContext != null) {
try {
getSelfInitializer().onStartup(localServletContext);
} catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}

#getSelfInitializer() 方法,便涉及到了我们最为关心的初始化流程,所以接着连接到了第三层。

第三层:getSelfInitializer()

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
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return new ServletContextInitializer() { // 匿名内部类

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
selfInitialize(servletContext);
}

};
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareEmbeddedWebApplicationContext(servletContext);
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
beanFactory);
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
getServletContext());
existingScopes.restore();
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
getServletContext());
// 第四层的入口
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}

EmbeddedWebApplicationContext 中的匿名类,没错了,就是这里的 #getSelfInitializer() 方法创建的!

为什么要这么设计?

这是典型的回调式方式,当匿名 ServletContextInitializer 类被 TomcatStarter 的 #onStartup() 方法调用,设计上是触发了 #selfInitialize(ServletContext servletContext) 方法的调用。

为什么 TomcatStarter 中没有出现 RegistrationBean

其实是隐式触发了 EmbeddedWebApplicationContext 中的 #selfInitialize(ServletContext servletContext) 方法。这样,#selfInitialize(ServletContext servletContext) 方法中,调用 #getServletContextInitializerBeans() 方法

获得 ServletContextInitializer 数组就成了关键。�所以接着连接到了第四层。

第四层:getServletContextInitializerBeans()

1
2
3
4
5
6
7
8
9
10
/**
* Returns {@link ServletContextInitializer}s that should be used with the embedded
* Servlet context. By default this method will first attempt to find
* {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain
* {@link EventListener} beans.
* @return the servlet initializer beans
*/
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory()); //第五层的入口
}

这个 ServletContextInitializerBeans 类,就是用来加载 Servlet 和 Filter 的

第五层:ServletContextInitializerBeans 的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
this.initializers = new LinkedMultiValueMap<Class<?>, ServletContextInitializer>();
addServletContextInitializerBeans(beanFactory); // 第六层的入口
addAdaptableBeans(beanFactory);
List<ServletContextInitializer> sortedInitializers = new ArrayList<ServletContextInitializer>();
for (Map.Entry<?, List<ServletContextInitializer>> entry : this.initializers
.entrySet()) {
AnnotationAwareOrderComparator.sort(entry.getValue());
sortedInitializers.addAll(entry.getValue());
}
this.sortedList = Collections.unmodifiableList(sortedInitializers);
}

第六层:addServletContextInitializerBeans(beanFactory)

1
2
3
4
5
6
7
8
9
// ServletContextInitializerBeans.java

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(
beanFactory, ServletContextInitializer.class)) {
addServletContextInitializerBean(initializerBean.getKey(),
initializerBean.getValue(), beanFactory);
}
}
  • 调用 #getOrderedBeansOfType( beanFactory, ServletContextInitializer.class) 方法,便是去容器中寻找注册过得 ServletContextInitializer ,这时候就可以把之前那些 RegistrationBean 全部加载出来了。并且 RegistrationBean 还实现了 Ordered 接口,在这儿用于排序。
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
// ServletContextInitializerBeans.java

private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(
beanFactory, ServletContextInitializer.class)) {
addServletContextInitializerBean(initializerBean.getKey(),
initializerBean.getValue(), beanFactory);
}
}

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 的处理

EmbeddedWebApplicationContext加载流程总结

  • EmbeddedWebApplicationContext 的 #onRefresh() 方法,触发配置了一个匿名的 ServletContextInitializer
  • 这个匿名的 ServletContextInitializer 的 onStartup(ServletContext servletContext) 方法,会去容器中搜索到了所有的 RegistrationBean ,并按照顺序加载到 ServletContext 中。
  • 这个匿名的 ServletContextInitializer 最终传递给 TomcatStarter,由 TomcatStarter 的 onStartup 方法去触发 ServletContextInitializer 的 #onStartup(ServletContext servletContext) 方法,最终完成装配!

image.png

加载流程拾遗

TomcatStarter 既然不是通过 SPI 机制装配的,那是怎么被 Spring 使用的?

自然是被 new 出来的。在 TomcatEmbeddedServletContainerFactory#configureContext(Context context, ServletContextInitializer[] initializers) 方法中可以看到,TomcatStarter 是被主动实例化出来的,并且还传入了 ServletContextInitializer 的数组,和上面分析的一样,一共有三个 ServletContextInitializer,包含了 EmbeddedWebApplicationContext 中的匿名实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// TomcatEmbeddedServletContainerFactory.java

protected void configureContext(Context context,
ServletContextInitializer[] initializers) {
// <1>
TomcatStarter starter = new TomcatStarter(initializers);
// <2>
if (context instanceof TomcatEmbeddedContext) {
// Should be true
((TomcatEmbeddedContext) context).setStarter(starter);
}
context.addServletContainerInitializer(starter, NO_CLASSES);
// ... 省略无关代码
}
  • 通过 context instanceof TomcatEmbeddedContext 判断使用的是内嵌的 Tomcat
  • SpringServletContainerInitializer 替换成了 TomcatStarter 类。

TomcatEmbeddedServletContainerFactory 又是如何被声明的?

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
// EmbeddedServletContainerAutoConfiguration.java

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication // web 环境下
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {

/**
* Nested configuration if Tomcat is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class }) // 不能再 Servlet + Tomcat 类
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {

@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory(); // TomcatEmbeddedServletContainerFactory 对象
}

}

// 省略 EmbeddedJetty

// 省略 EmbeddedUndertow
}

总结

image.png