DispatcherServlet的加载过程
DispatcherServlet 的获取
还 记 得 在 上 一 章 Web应 用 中 自 动 配 置 的 DispatcherServlet 和 DispatcherServletRegistra-tionBean 吗?当时只是将其实例化了,并未做其他处理。而在上节 WebServer 初始化的过程中又加载了它们。下面我们进行相关源码的解析。
在 ServletWebServerApplicationContext#createWebServer 方 法 中 , 调 用ServletWebServer-Factory 的 getWebServer 方法时,传递的参数是通过 getSelflnitializer方法获得的,这也是获取 DispatcherServlet 的入口方法。
再回顾一下相关代码。
代码语言:javascript复制private void createWebServer() {
WebServer webServer = this . WebServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactorv factorv= getWebServerFac
erector tarterxreterve
//第一处调用 getselfInitializer 方法
r(getSelfInitializer());
this.webServer = factory. getWebServe
else if (servletContext != null) {
try{
//第二处调用 getSelfInitializer 方法
getSelfInitializer(). onStartup(servletContext);
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet cont
ext", ex);
initPropertySources();
}
}
在上述代码中有两个分支逻辑都调用了 getelnitializer 方法。第一-处是当 WebServer 和ServletContext 对 象都不存在时,为了通过 ServletWebServerFactory 创建 WebServer 而将 其 结 果 作 为 参 数 传 入 。第 二 处 是 当 ServletContext 不 为 null 时 , 直 接 获 得ServletContex-lnitializer 并调用其 onStartup 方法来进行操作。
ServletWebServerApplicationContext 中 getSelflnitializer 方法代码如下。
代码语言:javascript复制private org.springframework. boot.web. servlet.ServletContextInitializer getSelfInitia-
lizer() {
return this: :selfInitialize;private void selfInitialize(ServletContext servletContext) throws ServletEx
ception
//通过指定的 servletContext 准备 webAppl icat ionContext,
//该方法类似 FContextL oaderL istener 遇常提供的功能
prepareWebApplicationContext(servletContext);
//通过 ServletContextScope 包装 ServletContext
//并将其注册为全局 web 应用范围( "appl icat ion")对应的值和注册为 ServletContext
类的属性
registerApplicationScope (servletContext);
WebApplicat ionContextUtils. registerEnvironmentBeans(getBeanFactory(), serv
letContext);
r (ServletContextInitializer beans : getServletContextInitiali z
()
beans.o
.onStartup(servletContext);
}
}
上述代码创建了-一个 ServletContextnitializer 接口的匿名实现类,并在具体实现中调用了当前类的 selflnitialize 方法。上 面的代码用到了 Java 8 的新特性(双冒号操作)和 lambda 表达式,没接触过的读者可能不太了解,下面将 getSelflnitializer 方法的代码用传统写法还原-下,就一目了然了。
代码语言:javascript复制private org. springframework . boot . web . servlet . ServletContextInitializer getS
Initializer() {
return new ServletContextInitializer() {
@0verride
public void onStartup(ServletContext servletContext) throws ServletEx
ception {
selfInitialize(servletContext);
}
也就是说在 getSelflnitializer 中定义了一个匿名的 ServletContextlnitializer 类,并且新建了一个对象。定义匿名类 ServletContextInitializer 的实现时, 其 onStartup 方法内调用了selflnitialize 方法。这里需注意 onStartup 中只是定义了具体实现,只有当调用该类的selflnitialize 方法时实现才 会被执行,此刻并没有真实调用。
在 selflnitialize 方法中,除了通过 ServletContext 准备 WebApplicationContext 和进行一些 注 册 ( 参 考 代 码 中 注 释 说 明 ) 操 作 外 , 最 重 要 的 就 是 通 过 for 循 环 中 的 getServletCont-extnitializerBeans 方法获得 ServletContextlnitializer 集合, 并遍历调用其元素的 onStartup 方法。
我 们 知 道 ServletContextlnitializer#onStartup 方 法 的 主 要 作 用 就 是 配 置 指 定 的Servlet-Context 所需的 Servlet、过滤器、监听器上下文参数和属性等。
关于 DispatcherServlet 的获取,继续看 getServletContextlnitializerBeans 方法。
代码语言:javascript复制protected Collection<ServletContextInitializer> getServletContextInitialize
Beans() {
return new ServletContextInitializerBeans (getBeanFactory());
}
getServletContextlnitializerBeans 方法只是创建了一个 ServletContextInitializerBeans 对象。
ServletContextInitializerBeans 其 实 就 是 一个 从 ListableBeanFactory 中 获 得 的ServletContext-Initializer 的集合。包括所有 ServletContextlnitializer 的 Bean,还适用于Servlet、Filter 和某些 EventListener 的 Bean。
ServletContextlnitializerBeans 的构造方法如下。
代码语言:javascript复制public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitial
izer>... 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.va
lues()
. stream() . flatMap((value) -> value . stream()
. sorted(Annotat ionAwareOrderComparator . INSTANCE))
. collect(Collectors. tolist());
this. sortedList = Collections . unmodifiablelist(sortedInitializers);
logMappings (this. initializers);
}
}
在 此调 用过 程中 ,构 造方 法的 initializerTypes 参 数为 空, 因此 该类 中的 成员 变量initializerTvpes 默认会被设置为只有一-个 ServletContextInitializer class 值的列表。
接 下 来 通 过 addServletContextlnitializerBeans 方 法 获 取 之 前 自 动 配 置 时 注 册 的Dispatch-erServletRegistrationBean.
代码语言:javascript复制private void addServletContextInitializerBeans(ListableBeanFactory beanEastory) {
//遍历 initial izerTypes 列表,这里很显然只有-个 ServletContextInitial izer.c
lass 值
for (Class<? extends ServletContextInitializer> initializerType : this. in
itia-
lizerTypes) {
//通过 getOrderedBeansOfType 方法获得 istableBeanFactory 中指定
//类型(这里重点是 ServletContextInitial izer)的 Bean name
//和对应 ServletContextInitial izer 实现的 Entry 列表
for (Entry<String, ? extends ServletContextInitializer> initializerBean
getOrderedBeansOfType(beanFactory, initializerType)) {
//将获得的 ServletContextInitial izer 对象添加到该类的成员变量
initial izers
addServletContextInitializerBean(initializerBean. getKey(),
initializerBean. getValue(), beanFactory);
private <T> List<Entry<String, T>> getOrderedBeans0fType(
ListableBeanFactory beanFactory, Class<T> type) {
return getOrderedBeansOfType(beanFactory, type, Collections . emptySet
());
private <T> List<Entry<String, T>> getOrderedBeansOfType(
ListableBeanFactory beanFactory, Class<T> type, Set<?> excludes) {
//根据类型从 ListableBeanFactory 中获取对应的 Bean 的 name 数组
String[] names = beanF actory . getBeanNamesForType(type, true, false);
Map<String, T> map = new LinkedHashMap<>();
//循环遍历,过滤排除,符合条件的封装在 Map 中
for (String name : names )
if (!excludes . contains(name) && !ScopedProxyUtils . isScopedTarget (nam
e)) {
T bean = beanFactory. getBean(name, type);
if (!exc ludes . contains(bean)) {
map. put(name, bean);
// Map 转换为 List,并排序返回
List<Entry<String, T>> beans = new ArrayList<>();
beans . addAll(map . entrySet());
beans . sort((o1, o2) -> AnnotationAwareOrderComparator . INSTANCE
. compare(o1. getValue(), o2.getValue()));
return beans;
}
在 addServletContextInitializerBeans 方法中,第二层循环调用了 getOrderedBeansOf-Type方法,其中第二个参数 initializerTypes 中的唯一值为 ServletContextlnitializer 类。
如果向上追溯 DispatcherServletRegistrationBean 类层级结构,会发现它其实就是一个ServletContextnitializer。那么,通过 getOrderedBeansOfType 便可将其从容器中查询出来,存储在 ServletContextlnitializerBeans 中。
在前面章节讲自动配置时,我们已经知道 DispatcherServletRegistrationBean 中存储着DispatcherServlet。至此,等于间接获得了 DispatcherServlet 的实例化对象。
ServletContextlnitializerBeans 构造方法中接下来的 addAdaptableBeans 又会加载默认的Filter 对象,比如 CharacterEncodingFilterRequestContextFilter 等,这里不再赘述。
经 过 . 上 面 的 代 码 追 踪 , 我 们 只 是 发 现 了 DispatcherServlet 通 过 DispatcherServletRegis-trationBean 被注册到了-一个 ServletContextlnitializer 匿名类中,但此时并没有触发加载的操作。下一 节,我们将继续追踪这个匿名类在什么时候被调用。
DispatcherServlet 的加载
要追踪 ServletContextlnitializer 匿名类(被存储在变长参数 initializers 中)何时被调用,还要回到 ServletWebServerApplicationContext 的 createWebServer 方法中,这里将 initializers作为参数传入了 ServletWebServerFactory 的 getWebServer 方法。
代码语言:javascript复制private void createWebServer() {
ServletWebServerFactory factory = getWebServerFactory();
this .webServer = factory . getWebServer (getSelfInitializer());
}
以Tomcat为 例 , 这 里 的ServletWebServerFactory其 实 就 是 TomcatServletWebServer-Factory类。TomcatServletWebServerFactory 的 getWebServer方法中又将 initializers 作为参数传递给了 prepareContext 方法。
代码语言:javascript复制protected void prepareContext (Host host, ServletContextInitializer[] initia
lizers) {
ServletContextInitializer[] initializersToUse = mergeInitializers(initial
izers);
configureContext(context, initializersToUse);
}
在 prepareContext 方法中,initializers 被合 并成 initialiersToUse 数组。这里的合并操作是由其抽象类 AbstractServletWebServerFactory 提供的 mergelnitializers 方法完成的。该合并操作是将指定的 ServletContexthnitializer 参数与当前实例中的参数组合在一起,供子类使用。这里的子类便是 TomcatServletWebServerFactory。
通过父类方法合并完成的参数 initializersToUse 又传递给了configureContext 方法。
代码语言:javascript复制protected void configureContext(Context context , ServletContextInitializer[]initializers) {
TomcatStarter starter = new TomcatStarter (initializers);
}
在 configureContext 方法中,我们首先完成了 TomcatStarter 的实例化操作。而 initializers也作为参数传递给了 TomcatStarter, 最终由 TomcatStarter 的 onStartup 方法去触发 Servlet-Contextlnitializer 的 onStartup 方法来完成装配。
代码语言:javascript复制@Override
Class<?>> classes, ServletContext servletContex
throws ServletException {
for (ServletContextInitializer initializer : this. initializers) {
initializer. onStartup(servletContext);
} catch (Exception ex) {
}
上面代码便是 TomcatStarter 的 onStartup 方法中遍历 initializers 并调用其 onStartup 方法来完成装配。顺便提一下 ,TomcatStarter 是在什么时间被触发的呢?在上节中讲到实例化TomcatWebServer 类对象时,其构造方法中调用了它自身的 initialize 方法,正是该 initialize方法最终触发了 TomcatStarter 的 onStartup 方法,代码如下。
代码语言:javascript复制private void initialize() throws WebServerException {
this. tomcat. start();
}
那么,调用了 ServletContextlnitializer 的 onStartup 方法就意味着 DispatcherServlet 被使用了吗?
的确如此 DispatcherServletRegistrationBean 本身就是一个 ServletContextlnitializer ,而其某层级的抽象父类 RegistrationBean 实现了 onStartup 方法。
代码语言:javascript复制public abstract class RegistrationBean implements ServletContextInitialize
r, Ordered {
@Override
public final void onStartup(ServletContext servletContext) throws Servl
etException {
String description = getDescription();
register(description, servletContext);
protected abstract void register(String description, ServletContext servl
etContext);
}
RegistrationBean 中实现的 onStartup 方法会调用 getDescription 方法和 register 方法。这两 个 方 法 均 为 抽 象 方 法 , 由 子 类 来 实 现 。其 中 getDescription 方 法 由ServletRegistration-Bean 实现,代码如下。
代码语言:javascript复制@Override
procecteastung BEDESCr PCLON L
neturn "servlet
getServletName();
register 的方法通过子类 DynamicRegistrationBean 实现。
代码语言:javascript复制@Override
protected final void register(String description, ServletContext servletCon
text) {
D registration = addRegistration(description, servletContext);
configure(registration);
}
DynamicRegistrationBean 类中同样只定义了 register 调用的抽象方法 addRegistrationaddRegistration 方法的具体实现依然在 ServletRegistrationBean 中。
代码语言:javascript复制@Override
protected ServletRegistrat ion. Dynamic addRegistration(String description, S
ervletContext servletContext) {
String name = getServletName();
return servletContext . addServlet(name, this . servlet);
}
addReqistration 方法中的 servlet 便是自动配置时传入 DispatcherServletRegistrationBean构造函数中的 DispatcherServlet。
看到这里我们已经知道 DispatcherServlet 的整个自动配置及加载过程的重要性了。
DispatcherServlet 是 整个 Spring MVC 的核心组件之一,通过这个核心组件的追踪和讲解,我们不仅知道了它在 Spring Boot 中的整个运作过程,而且能够学会-套分析、追踪代码实现的思路。更重要的是, 这是一个关于 Spring Boot、Spring MVC 以及内置 Servlet 知识的融合主线,对于有心的读者,可根据此主线无限学习、填充自己在此过程中遇到的知识点。
综合实战
在以上几节中我们通过讲解源码学习了基于 Tomcat 的 Servlet 容器的初始化步骤,在实战中针对以上步骤并不需要过多干预,使用最多的场景就是通过 application 配置文件对Servlet 容器进行一些定制化的参数配置, 配置参数对应于 ServerProperties 类中的属性。
在本节实例中,我们将基于源代码了解了 Servlet 的基本流程和内部原理,可以通过代码的形 式 来 对 Servlet 容 器 进 行 配 置 。首 先 , 可 以 通 过 上 面 多 次 在 源 码 中 提 到 的 WebServer-FactoryCustomizer 接口来实现代码形式的定制化配置。基本使用示例如下。
代码语言:javascript复制@Componentpublic class CustomServletContainer implements WebServerF actoryCustomizer<C
figurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
factory. setPort( 8081);
}
}
通过上述方式修改代码之后,我们再次启动项目,项目端口已经变为 808 了。同样的,也可以针对 factory 的其他属性进行配置(调用对应的 set 方法)。如果上述配置也无法满足业务需求,则可通过进一步实现容器的工厂 方法进行定制化操作。
比 如 , Tomcat 、 Jetty 、 Undertow 容 器 的 定 制 化 可 分 别 注册 TomcatServletWebServer-Factory、JettyServletWebServerFactory、UndertowServletWebServerFactory 对应的 Factory 来实现。下 面以 Tomcat 的基本配置为例进行讲解,示例代码如下。
代码语言:javascript复制@Configuration
public class TomcatConfig {
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFacto
ry();
factory. setPort(8081);
Session session = new Session();
session. setTimeout (Duration. ofMinutes(30L));
factory. setSession(session);
Set<ErrorPage> errorPages = new HashSet<>();
ErrorPage errorPage = new ErrorPage("/error");
errorPages . add(errorPage);
factory . setErrorPages(errorPages);
return factory;
}
在上述方法中,通过创建 TomcatServletWebServerFactory 的对象,并进行具体参数的设置来完成容器的自定义。在本节两个示例中,需要注意的是,如果 Servlet 容器的端口和Tomcat 的端口同时配置,则 Tomcat 的端口不会生效。
通过上述两种形式都可以对内置容器进行定制化配置,但一般情况下,采用默认配置或通过属性配置即可。如果上述两种配置都无法满足需求,可考虑不使用内置容器,而是将项目打包成可发布到外部容器的 WAR 形式。关于 Spring Boot 项目如何打成 WAR 包,在后面的章节中会详细介绍。
小结
本章重点以内置 Tomcat 为例讲解了 Spring Boot 中 Servlet 容器的初始化及启动,其实在这个过程中经历了许多过程,而每部分都可以拓展出很大篇幅,我们以学习思路为重点,相关知识点学习或温故为辅助。现在,读者朋友可针对其他 Servlet 容器的初始化过程进行验证性学习。
本文给大家讲解的内容是SpringBoot内置Servlet容器源码解析:DispatcherServlet的加载过程
- 下篇文章给大家讲解的是SpringBoot数据库配置源码解析;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!
本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。