概述
在 Java - Servlet 3.0 这篇文章中,我们知道在 Servlet 3.0
之后,可以使用 注解来配置 servlet
以及 运行时插件可插拔
能力。
SpringBoot(使用版本:2.3.12.RELEASE)
为我们提供了开箱即用的 web 环境,当然也提供了与 Servlet 3.0
的集成,核心在 org.springframework.boot.web.servlet
包中:
ServletContextInitializer
在 Java - Servlet 3.0 这篇文章中,我们知道 Servlet 3.0
提供了 ServletContainerInitializer
接口来提供插件化能力,SpingBoot
提供了 ServletContextInitializer
接口,以编程的方式来配置 Servlet 3.0+ 上下文
,但是生命周期是由 Spring
来管理的,而不是 Servlet 容器
。
集成 Servlet 容器
在 SpringBoot - 内嵌 tomcat 等 servlet 容器的原理 这篇文章中,我们知道 SpringBoot
默认使用 Tomcat
作为 servlet 容器
,在 SpringBoot
的启动流程中,ServletWebServerApplicationContext
类中的 createWebServer()
方法用于创建 WebServer
。
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
// [1]getSelfInitializer():返回用于完成 WebApplicationContext 设置的 ServletContextInitializer
// [2]getWebServer():会完成 ServletContextInitializer 转换为 ServletContainerInitializer
this.webServer = factory.getWebServer(getSelfInitializer());
...
}
...
}
[1] getSelfInitializer()
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
// 准备 WebApplicationContext
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// getServletContextInitializerBeans:获取所有 ServletContextInitializer 类型的 Bean
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
查看 getServletContextInitializerBeans()
方法,返回了一个集合:ServletContextInitializerBeans
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
ServletContextInitializerBeans
ServletContextInitializerBeans
类实现了 AbstractCollection
,并重写了 iterator()
和 size()
,遍历的自然也就是 iterator()
中使用的成员变量 sortedList
public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> {
...
private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;
private final List<Class<? extends ServletContextInitializer>> initializerTypes;
private List<ServletContextInitializer> sortedList;
...
@Override
public Iterator<ServletContextInitializer> iterator() {
return this.sortedList.iterator();
}
@Override
public int size() {
return this.sortedList.size();
}
...
}
查看 ServletContextInitializerBeans
集合类的构造器:
@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
this.initializers = new LinkedMultiValueMap<>();
// 这里我们没有在构造器中传 initializerTypes,所以默认为 ServletContextInitializer
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);
}
查看 addServletContextInitializerBeans(beanFactory)
方法:
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
// 这里的 this.initializerTypes 就是 ServletContextInitializer
for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
for (Entry<String, ? extends ServletContextInitializer> initializerBean :
// 获取所有 initializerType 类型的 Bean 并排序
getOrderedBeansOfType(beanFactory, initializerType)) {
// 添加 ServletContextInitializerBean 到成员变量:initializers 中,是一个 Spring 实现 Map 接口的 MultiValueMap 类型
addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
}
}
}
打断点获取到 getOrderedBeansOfType()
方法返回的值,默认是 DispatcherServletRegistrationBean
:
接着遍历上面 getOrderedBeansOfType()
方法中获取到的 Bean
,判断是属于 ServletRegistrationBean
、FilterRegistrationBean
、DelegatingFilterProxyRegistrationBean
、ServletListenerRegistrationBean
,并添加到成员变量:initializers
中:
private void addServletContextInitializerBean(Class<?> type, String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory, Object source) {
this.initializers.add(type, initializer);
...
}
接着在构造器中按指定规则排序并赋值给成员变量 sortedList
:
@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
...
// 赋值 sortedList
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);
}
接着会遍历 sortedList
,并调用其中的 Bean
的 onStartUp
方法:
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
// 调用 onStartUp 方法
beans.onStartup(servletContext);
}
}
[2] getWebServer()
ServletWebServerFactory
SpringBoot
对所支持的 Servlet 容器
做了抽象处理,提取出了工厂类:ServletWebServerFactory
@FunctionalInterface
public interface ServletWebServerFactory {
WebServer getWebServer(ServletContextInitializer... initializers);
}
常用的 Servlet 容器
实现:
Servlet 容器类型 | WebServer 模型接口 | WebServer 工厂实现类 |
---|---|---|
Tomcat | TomcatWebServer | TomcatServletWebServerFactory |
Jetty | JettyWebServer | JettyServletWebServerFactory |
Undertow | UndertowWebServer | UndertowServletWebServerFactory |
类路径下有且只能有一个 ServletWebServerFactory
的实现类:
protected ServletWebServerFactory getWebServerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
private void createWebServer() {
...
this.webServer = factory.getWebServer(getSelfInitializer());
...
}
从代码中可以看到,getWebServer
是把 getSelfInitializer()
返回的 ServletWebServerApplicationContext
作为参数:
查看 TomcatServletWebServerFactory
中的 getWebServer
方法:
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
...
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
...
context.addLifecycleListener(new StaticResourceConfigurer(context));
// 组合 SpringBoot 需要初始化的所有 ServletContextInitializer
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
host.addChild(context);
// 配置上下文环境
configureContext(context, initializersToUse);
postProcessContext(context);
}
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
context.addServletContainerInitializer(starter, NO_CLASSES);
for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
context.addLifecycleListener(lifecycleListener);
}
...
}
TomcatStarter
是 SpringBoot
对于 嵌入式 Tomcat
的定义,实现了 ServletContainerInitializer,查看源码:
class TomcatStarter implements ServletContainerInitializer {
private static final Log logger = LogFactory.getLog(TomcatStarter.class);
private final ServletContextInitializer[] initializers;
private volatile Exception startUpException;
// 会把 ServletContextInitializer 作为构造器参数传进来
TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
// Tomcat 启动的时候会调用 onStartUp 方法
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
catch (Exception ex) {
this.startUpException = ex;
// Prevent Tomcat from logging and re-throwing when we know we can
// deal with it in the main thread, but log for information here.
if (logger.isErrorEnabled()) {
logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
+ ex.getMessage());
}
}
}
Exception getStartUpException() {
return this.startUpException;
}
}
总结
通过以上分析,源码追踪,我们知道了 SpringBoot
是如何结合 Tomcat
等 Servlet 容器
实现 Servlet
、Filter
、Listerner
等组件与 Spring IoC 容器
结合并实现加载和初始化的。
从 ServletContextInitializer
的关系类图中可以看到,SpringBoot
定义了 ServletRegistrationBean
、ServletListenerRegistrationBean
、FilterRegistrationBean
、DelegatingFilterProxyRegistrationBean
等基于 ServletContextInitializer
接口的实现类来实现组件的加载,在 SpringBoot 学习专栏 中会具体讲解每一种 RegistrationBean
的用法,欢迎订阅关注。