内置容器源码解析:DispatcherServlet加载过程

1496 篇文章 10 订阅
1494 篇文章 14 订阅

DispatcherServlet的加载过程

DispatcherServlet 的获取

还 记 得 在 上 一 章 Web应 用 中 自 动 配 置 的 DispatcherServlet 和
DispatcherServletRegistra-tionBean 吗?当时只是将其实例化了,并未做其他处理。而在上节 WebServer 初始化的过程中又加载了它们。下面我们进行相关源码的解析。


ServletWebServerApplicationContext#createWebServer 方 法 中 , 调 用ServletWebServer-Factory 的 getWebServer 方法时,传递的参数是通过 getSelflnitializer方法获得的,这也是获取 DispatcherServlet 的入口方法。

再回顾一下相关代码。

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 方法代码如下。

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 方法的代码用传统写法还原-下,就一目了然了。

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 方法。

protected Collection<ServletContextInitializer> getServletContextInitialize
Beans() {
return new ServletContextInitializerBeans (getBeanFactory());
}


getServletContextlnitializerBeans 方法只是创建了一个 ServletContextInitializerBeans 对象。


ServletContextInitializerBeans 其 实 就 是 一个 从 ListableBeanFactory 中 获 得 的ServletContext-Initializer 的集合。包括所有 ServletContextlnitializer 的 Bean,还适用于Servlet、Filter 和某些 EventListener 的 Bean。


ServletContextlnitializerBeans 的构造方法如下。

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.

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 方法。

private void createWebServer() {
ServletWebServerFactory factory = getWebServerFactory();
this .webServer = factory . getWebServer (getSelfInitializer());
}

以Tomcat为 例 , 这 里 的ServletWebServerFactory其 实 就 是
TomcatServletWebServer-Factory类。TomcatServletWebServerFactory 的 getWebServer方法中又将 initializers 作为参数传递给了 prepareContext 方法。

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 方法。

protected void configureContext(Context context , ServletContextInitializer[]initializers) {
TomcatStarter starter = new TomcatStarter (initializers);
}

在 configureContext 方法中,我们首先完成了 TomcatStarter 的实例化操作。而 initializers也作为参数传递给了 TomcatStarter, 最终由 TomcatStarter 的 onStartup 方法去触发
Servlet-Contextlnitializer 的 onStartup 方法来完成装配。

@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 方法,代码如下。

private void initialize() throws WebServerException {
this. tomcat. start();
}

那么,调用了 ServletContextlnitializer 的 onStartup 方法就意味着 DispatcherServlet 被使用了吗?

的确如此
DispatcherServletRegistrationBean 本身就是一个 ServletContextlnitializer ,而其某层级的抽象父类 RegistrationBean 实现了 onStartup 方法。

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 实现,代码如下。

@Override
procecteastung BEDESCr PCLON L
neturn "servlet
getServletName();

register 的方法通过子类 DynamicRegistrationBean 实现。

@Override
protected final void register(String description, ServletContext servletCon
text) {
D registration = addRegistration(description, servletContext);
configure(registration);
}

DynamicRegistrationBean 类中同样只定义了 register 调用的抽象方法
addRegistrationaddRegistration 方法的具体实现依然在 ServletRegistrationBean 中。

@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 接口来实现代码形式的定制化配置。基本使用示例如下。

@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 的基本配置为例进行讲解,示例代码如下。

@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的加载过程

  1. 下篇文章给大家讲解的是SpringBoot数据库配置源码解析;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring是一个非常流行的开源框架,主要用于构建企业级Java应用程序。Spring框架的核心是IoC(控制反转)和AOP(面向切面编程)两个特性。下面是Spring框架的源码解析: 1. IoC容器 Spring的IoC容器是Spring框架的核心,它通过反射机制实现了对象的创建和依赖注入。在IoC容器中,所有的对象都是由容器来创建和管理的,而不是由程序员来创建和管理。IoC容器的实现主要包括BeanFactory和ApplicationContext两个接口以及它们的实现类。其中,BeanFactory是Spring的基础设施,而ApplicationContext则是BeanFactory的扩展,提供了更多的功能和特性。 2. AOP Spring的AOP框架是基于动态代理实现的,它提供了面向切面编程的能力。在AOP中,切面是一个横跨多个对象的切入点,它可以在应用程序的不同层次上实现横切关注点的功能,比如事务管理、安全认证、性能统计等。AOP框架的实现主要包括切点、通知、切面和代理等四个概念。 3. MVC框架 Spring的MVC框架是基于Servlet API实现的,它提供了一个灵活、可扩展的Web应用程序框架。MVC框架的核心是DispatcherServlet,它是Spring MVC的前置控制器,负责接收所有的HTTP请求并进行分发和处理。MVC框架还包括HandlerMapping、Controller、ViewResolver等组件,用于实现请求的映射、处理和视图渲染等功能。 4. 数据访问 Spring框架提供了一系列的数据访问技术,包括JDBC、ORM和事务管理等。在JDBC方面,Spring提供了JdbcTemplate和NamedParameterJdbcTemplate两个类,用于简化JDBC的使用。在ORM方面,Spring支持多种ORM框架,包括Hibernate、MyBatis等。在事务管理方面,Spring提供了声明式事务管理和编程式事务管理两种方式。 总的来说,Spring框架是一个非常庞大、复杂的框架,它涵盖了众多的技术和组件。要深入了解Spring框架的源码,需要对Java编程、反射机制、设计模式、Servlet API等多个方面有深入的了解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值