热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringBootWeb应用源码解析:遗失的web.xmI

SpringBootWeb应用源码解析在Spring及SpringBoot的使用过程中,应用最广泛的当属Web应用,而Web应用又往往部署在像Tomc

SpringBootWeb应用源码解析

在 Spring 及 Spring Boot 的使用过程中,应用最广泛的当属 Web 应用,而 Web 应用又往往部署在像 Tomcat 这样的 Servlet 容器中。

本章将带领大家学习 Spring Boot 中 Web 应用的整合以及在此过程中与直接使用 Spring 的差别。


遗失的web.xmI

提到 Spring 的 Web 应用,我们首先想到的可能是 Spring MVC 框架和 web.xmI 等配置文件。而 Spring MVC 又是围绕 DispatcherServlet 这个核心类来展开的。

Spring Boot 当前是基于 Spring 5.2.x 版本,和传统的 Spring 启动有所不同。以前是通过在web.xml 中 配 置 Servlet 来 完 成 Spring MVC 的 启 动 , 但 在 Spring Boot 中 是 通 过Dispatch-erServletAutoConfiguration 来完成初始化工作的。在此过程中,web.xml 遗失了。

我们先回顾一下 Servlet 3.0 之前版本的操作。当我们创建一个 web 项目时,往往会在resources/WEB-INF 目录下创建一个 web.xml 文件, 该文件内配置了 Servlet 和 Filter 等功能。当 Spring MVC 出现后,web.xml 中 便有了 DispatcherServlet 的配置。

随着 Java EE 6 的发布,Servlet 3.0 作为 Java EE 6 规范体系的一员,也被慢慢推广并被用户接受。 Servlet 3.0 在 Servlet 25 的基础. 上提供了一些简化 Web 应用的开发和部署的新特性,无 xml 配置便是其中一项。

Servlet 3.0 提供了@WebServlet、@WebFilter 等注解, 可以使用注解来声明 Servlet 和Filter,这便有了抛弃 web.xml 的基础。同时,它还提供了可以在运行时动态注册 Servlet、Filter、 Listener 等更加强大的功能。 关于动态配置 Servlet,如果翻看 Servlet 3.0 中servlet-api 包下 ServletContext 接口定义的方法,你会看到如下方法定义。

public ServletRegistration. Dynamic addServlet(String servletName, String clas sName);

public ServletRegistration . Dynamic addServlet(String servletName, Servlet s

ervlet);

public ServletRegistration . Dynamic addServlet(String servletName,Class

xtends Servlet> servletClass);public T createServlet(Class clazz)throws ServletExc

eption;

public ServletRegistration getServletRegistration(String servletName);

public Map getServletRegistrations

();

}

在 Servlet 3.0 中还新增了 ServletContainerlnitializer 接口,在容器启动时使用 JAR 服务 API来发现其实现类,并将容器 WEB-INF/ib 目录下 jar 包中的类都交由该类的 onStartup 方法来处理。而 Servlet 和 Filter 在 Web 应用启动时可借助该接口的实现类和 Java 的 SPI 机制完成加载。

在Spring中 提 供 了ServletContainerlnitializer接 口 的 实 现 类SpringServletContainerlni-tializer, 该 类 在 其 onStartup 方 法 中 会 调 用 所 有WebApplicationInitializer 实现类的 onStartup 方法,将相关组件注册到容器中。而 Servlet和 Filter 也是 通过 WebApplicationitializer 的实现类完成创建和加载的。

基于以上新特性和演变,当我们使用 Spring Boot 时,Spring Boot 已经不知不觉地开始使用这些新特性了。至此,我们从发展的角度了解了 web.xml 消失的过程,在后面的章节我会详细讲解 Spring Boot 是如何进行 Web 应用的自动配置的。

SpringBootWeb应用源码解析:遗失的web.xmI


Web应用的自动配置

在 Spring Boot 项目中引入 spring-boot-starter-web 的依赖,Spring Boot 的自动配置机制便会加载并初始化其相关组件。整个自动配置原理在前面章节已经讲过,这里针对 Web 应用再进行一-次梳理。

在上一节中我们已经提到,Servlet3.0 中新增了ServletContainerlnitializer 接 口 , 而 在 Spring 中 又 提 供 了 其 实 现 类SpringServletContainerlnitializer 来 初 始 化 Servlet 。 但 在 Spring Boot 中 , 当 引 入spring-boot-starter-web 的依赖之后,Spring Boot 并未完全遵守 Servlet 3.0 的规范,也没有使用 Spring 中提供的 SpringServletContainerlnitializer 类,而是完全选择另外一套初始化流程。下面, 我们看一下初始化流程的源码。

根据自动配置的原理,我们在 spring-boot-autoconfigure 包中的 META-INF/spring.factories配置文件中找到了针对 Servlet 自动配置的 EnableAutoConfiguration。

#自动配置

org. springframework . boot . autoconfigure . EnableAutoConfiguration=\

org. springframework. boot . autoconfigure . web. servlet . DispatcherServletAutoCon下面对 EnableAutoConfiguration 类中的自动配置项目进行逐步分析。

DispatcherServlet 自动配置

DispatcherServlet 自动配置位于 DispatcherServletAutoConfiguration 类中。下面我们通过DispatcherServletAutoConfiguration 的源码来 了解其自动配置的过程。

首先来看 DispatcherServletAutoConfiguration 上面的注解。

@Configuration(proxyBeanMethods = false)

@ConditionalOnWebApplication(type = Type . SERVLET)

@ConditionalOnClass(DispatcherServlet. class)

@AutoConfigureAfter(ServletwWebServerFactoryAutoConfiguration. class)

public class DispatcherServletAutoConfiguration {}

这些注解的基本功能在前面章节已经提到过,我们再来温习一下。


  • @AutoConfigureOrder:指定自动配置加载的顺序。
  • @Configuration:指定该类为配置类,交给 Spring 容器管理,默认指定不使用代理。
  • .@ConditionalOmWebApplication:表示只有 Web 应用才会加载此类。
  • @ConditionalOnClass:表示只有存在 DispatcherServlet 类的情况下才会加载此类。
  • . @AutoConfigureAfter : ServletWebServerFactoryAutoConfiguration 加载完成之后才会加此 类。

关于 ServletWebServerFactoryAutoConfiguration 的加载会在下一章中详细讲解。

从整体上来看,在 DispatcherServletAutoConfiguration 内部主要提供 了 4 个静态内部类。


  • :DispatcherServletConfiguration:主 要用来初始化 DispatcherServlet。
  • :DispatcherServletRegistrationConfiguration :主要用来将 DispatcherServlet 注册到系统中。
  • :DefaultDispatcherServletCondition :主要针对容器中 DispatcherServlet 进行一些逻辑判断。
  • :DispatcherServletRegistrationCondition :

主要针对注册 DispatcherServlet 进行一些逻辑判断。

针对以上概述,下面来看具体的源代码实现。首先是内部类 DispatcherServletConfiguration的源码及功能。

@Configuration(proxyBeanMethods = false)

//实例化配置类

@Conditional (DefaultDispatcherServletCondition.class) //实例化条件:通过该

@ConditionalOnClass (ServletRegistration. class)

//存在指定的 ServletRe

gistration 类//加裁 HttpProperties 和 webMvcProperties

@EnableConfigurationProperties({ HttpProperties. class, WebMvcProperties .cla

s })

protected static class DispatcherServletConfiguration {

@Bean(name = DEFAULT DISPATCHER_ SERVLET_ BEAN NAME)

public DispatcherServlet dispatcherServlet (HttpProperties httpProperties ,

WebMvcProperties webMvcProperties) {

//创建 DispatcherServlet

DispatcherServlet dispatcherServlet = new DispatcherServlet();

//初始化 DispatcherServlet 各项配置

dispatcherServlet . setDispatchOptionsRequest (webMvcProperties . isDispatch

OptionsRequest());

dispatcherServlet . setDispatchTraceRequest (webMvcProperties . isDispatch-

TraceRequest());

dispatcherServlet. setThrowExceptionI fNoHandlerFound(webMvcProperties. is

ThrowExcept ionIfNoHandlerFound();

di spatcherServlet . setPublishEvents (webMvcProperties . isPublishRequestHan

dledEvents());

dispatcherServlet . setEnableLoggingReques tDetails (httpProperties. isLog-

RequestDetails());

return dispatcherServlet;

//初始化上传文件的解析器

@Bean

@ConditionalOnBean(MultipartResolver . class)

@ConditionalOnMissingBean(name = DispatcherServlet . MULTIPART_ RESOLVER_

BEA

NAME)

public MultipartResolver multipartResolver(MultipartResolver resolver) {

//检测用户是否创建了 MultipartResolver, 但命名不正确

return resolver;

}

}

通过以上源码可以看出,当满足指定的条件后,会对 DispatcherServletConfiguration 进行实例化,而该类内部通过@Bean 注解的方法会被实例化,生成 Bean 并注入 Spring 容器中。

其中,在 DispatcherServlet 方法中完成 了 DispatcherServlet 的实例化和基本设置。这里既没有用到 SPI 机制,也没用到 Spring 提供的 SpringServletContainerlnitializer 和 Servlet3.0 的 ServletContainerlnitializer 接口,从而验证了 6.2 节提到的说法。

DispatcherServlet 方法将 DispatcherServlet 实例化的 Bean 注入 Spring 容器中,并且指定Bean 的 name 为 dispatcherServlet。该名称默认会被映射到根 URL 的/访问路径。

DispatcherServlet 作为前端控制器设计模式的实现,提供了 Spring WebMVC 的集中访问点,负责职责的分派,与 Spring loc 容器无缝集成,可以获得 Spring 的所有好处。它的主要作用包括:文件上传解析、请求映射到处理器、通过 ViewResolver 解析逻辑视图名到具体视图实现、本地化解析、渲染具体视图等。

在未使用 Spring Boot 时,通常 DispatcherServlet 类的配置需要开发人员在 web.xml 当中进行配置。这里 Spring Boot 通过自动配置完成了 DispatcherServlet 类的配置和初始化,并 在 此 过 程 中 设 置 了 一 些 初 始 化 的 参 数 。 这 些 参 数 通 过 HttpProperties 和WebMvcProperties 获得。

DispatcherServletConfiguration 中还定义了上传文件的解析器 MultipartResolver 的 Bean初始化操作,准确来说是 Bean 名称转化的操作。通过条件注解判断,当 MultipartResolver的 Bean 存在,但 Bean 的名称不为“multipartResolver”时,将其重命名为“multipartResolver'我们再回到 DispatcherServletConfiguration 的注解部分,@Conditional 指定的限定条件类为 DefaultDispatcherServletCondition,该类是 DispatcherServletAutoConfiguration 的另外一个内部类,代码如下。

@Order (Ordered. LOWEST_ PRECEDENCE- 10)

private static class DefaultDispatcherServletCondition extends SpringBootCo

ndition {

@Override

public ConditionOutcome getMatchOutcome (ConditionContext context ,

AnnotatedTypeMetadata metadata) {

ConditionMessage . Builder message = ConditionMessage

. forCondition("Default DispatcherServlet");

ConfigurablelistableBeanFactory beanFactory = context. getBeanFactory();

//获取类型为 Di spatcherServlet 的 Bean 名称列表

List dispatchServletBeans = Arrays . asList(beanFactory

. getBeanNamesForType

(DispatcherServlet.class, false, false));

//如果 Bean 名称列表中包含 dispatcherServlet,则返回不匹配

if (dispatchServletBeans. contains (DEFAULT_ DISPATCHER_ SERVLET_ BEAN_ NAM

E)) {

return ConditionOutcome . noMatch (message . found("dispatcher servlet bea

n")

. items (DEFAULT_ DISPATCHER_ SERVLET_ BEAN_ NAME));

// 如果 beanFactory 中包含名称为 Di spatcherServlet 的 Bean, 则返回不匹配

if (beanFactory. containsBean(DEFAULT_ DISPATCHER_ SERVLET_ BEAN NAME)) {

return ConditionOutcome

. noMatch(message . found("non dispatcher servlet bean")

. items (DEFAULT_ DISPATCHER_ SERVLET_ BEAN_ NAME));

//如果 Bean 名称列表为空,则返回匹配

if (dispatchServletBeans . isEmpty()) {

return ConditionOutcome

. match(message . didNotFind(”dispatcher servlet beans").atAll());

//其他情况则返回匹配

return ConditionOutcome . match(message

. found("dispatcher servlet bean", "dispat

cher servlet beans")

. items (Style . QUOTE, dispatchServletBeans)

. append("and none is named ”+ DEFAULT_ DI

SPATCHER_ SERVLET_ BEAN_

NAME));

}

}

DefaultDispatcherServletCondition 的最核心业务逻辑只做了一件事,那就是:防止重复生成DispatcherServlet。具体实现流程为:从上下文中获得 beanFactory, 然后通过 beanFactory获取类型为 DispatcherServlet 的 Bean 的 name 列表,然后分别 判断 name 列表和beanFactory 中是否包含名称为“dispatcherServlet"的字符串或 Bean,如果包含则返回不匹配(已经存在,不匹配则不会重复实例化),否则返回匹配。

DispatcherServletConfiguration 类中还有-个注解@EnableConfigurationProperties,该注解指定了两个配置类:

HttpProperties 和 WebMvcProperties。这两个配置类正是上面初始化 DispatcherServlet 时用于初始化的参数值,查看这两个类的源代码就会发现,它们分别对应加载了以“spring.http"和"spring.mvc"为前缀的配置项,可以在 application properties 文件中进行配置。

@ConfigurationProperties(prefix = "spring . http" )

public class HttpProperties {}

@ConfigurationProperties(prefix = "spring . mvc")

public class WebMvcProperties {}

SpringBootWeb应用源码解析:遗失的web.xmI

DispatcherServletRegistrationBean 自动配置

下面我们再来看用于注册的 DispatcherServletRegistrationConfiguration 类。关于该类的注解功能可以参考 DispatcherServletConfiguration 的说明,我们主要看:业务逻辑处理。

@Configuration( proxyBeanMethods = false)

@Conditional (DispatcherServletRegistrationCondition. class)

@ConditionalOnClass (ServletRegistration. class)

@EnableConfigurationProperties (WebMvcProperties .class)

@Import (DispatcherServletConfiguration.class)

protected static class DispatcherServletRegistrationConfiguration {

@Bean(name = DEFAULT_ DISPATCHER_ SERVLET_ REGISTRATION_ BEAN AME)

@ConditionalOnBean(value=

DispatcherServlet.class,

name=DEFAULT_

DISPATCHER_

SERVLET_ BEAN_ NAME)

public

DispatcherServletRegistrationBean

di

spatcherServletRegistration(DispatcherServlet dispatcherServlet,

n We

bMvcProperties webMvcProperties, objectProvider ul

tipartConfig) {

//通过 ServletRegistrat ionBean 将 dispatcherServlet 注册为 Servlet,这样 serv

Let 才会生效

Di spatcherServletRegistrationBean registration = new DispatcherServletR

egistrationBean( dispatcherServlet ,

webMvcProperties . getServlet(). getPath());

//设置名称为 di spatcherServlet

registration. setName(DEFAULT_ DISPATCHER_ SERVLET_ BEAN NAME);

// 没 置 加 载 优 先 级 , 没 置 值 默 认 为 -1 , 存 在 FwebMvcProperties 类 中 registration.

setLoadOnStartup(webMvcProperties . getServlet(). getLoadOn-Startup());

multipartConfig. ifAvailable(registration::setMultipartConfig);

return registration;

}

}

DispatcherServletRegistrationConfiguration 类的核心功能就是注册 dispatcherServlet,使其生效并设置一一些初始化的参数。

其 中 DispatcherServletRegistrationBean 继 承 自 ServletRegistrationBean , 主 要 为DispatcherServlet 提供服务。

DispatcherServletRegistrationBean 和 DispatcherServlet 都提 供了注 册 Servlet 并公开DispatcherServletPath 信息的功能。

在 dispatcherServletRegistration 方 法 中 直 接 通 过 new 来 创 建DispatcherServletRegistrationBean,第一个 参数为 DispatcherServlet,第二个参数为application 配置文件中配置的 path 值,相关代码如下。

public class DispatcherServletRegistrationBean extends ServletRegistratiomB

ean implements Di spatcherServletPath {

private final String path;

//根据指定的 DispatcherServlet 和给定的 path 创建 DispatcherServletRegistratio

nBean

public DispatcherServletRegistrationBean(DispatcherServlet servlet, Strin3 path) {

super(servlet);

Assert . notNull(path, "Path must not be null");

this.path = path;

super . addUrlMappings (getServletUrlMapping());

/重写父类的方法,但抛出异常,相当于禁用该操作

@Override

public void setUrlMappings (Collection urlMappings) {

throw new UnsupportedOperationException("URL Mapping cannot be changed

on a DispatcherServlet registration");

//重写父类的方法,但抛出异常,相当于禁用该操作

@verride

public void addUrlMappings(String... urlMappings) {

throw new UnsupportedOperationException("URL Mapping cannot be changed

on a DispatcherServlet registration");

}

}

在 DispatcherServletRegistrationBean 中实现了 setUrlMappings 和 addlUrlManninas 两种方法,但均直接抛出异常,这相当于禁用了该子类中这两项操作。而在构造方法中除了成员变 量 赋 值 之 外 , 还 调 用 了 父 类 ServletRegistrationBean 的 构 造 方 法ServletRegistrationBean(Tservlet,String...urlMappings)和addUrlMappings(String...urlMappings)方法。

关于 ServletRegistrationBean 的构造方法和 addUrlMappings 只是进行成员变量的赋值和设 置 , 我 们 重 点 看 DispatcherServletRegistrationBean 在 构 造 方 法 中 调 用 的getServletUrlMapping 方法。顾名思义,该方法的功能是获取 Servlet 的 URL 的匹配。该方法具体在 DispatcherServletRegistrationBean 实现的接口 DispatcherServletPath 中定义。

DispatcherServletPath 的 主 要 功 能 是 提 供 自 动 配 置 所 需 的 path 信 息 , 而DispatcherServletRegistrationBean 中的 path 信息正是构造方法传入的。

下面我们来看 DispatcherServletPath 中 getServletUrlMapping 方法的具体实现。

@FunctionalInterface

public interface DispatcherServletPath {

//返回一个 URL 匹配表达式,用 FServletRegistrat ionBean 映射对应的 DispatcherSe

rvlet

default String getServletUrlMapping() {

if (getPath(). equals("") || getPath(). equals("/")) {

return "/";

if (getPath(). contains("*")) {

return getPath();

if (getPath(). endsWith("/")) {

return getPath() + *";

return getPath() + "/*";

}

}

getServletUrlMapping 方法为接口的默认实现方法,返回一个用于 ServletRegistrationBean映射 DispatcherServlet 的 URL 表达式。判断逻辑比较简单:如果获得的 path 为空或“/”,则返回“/”; 如果 path 中包含“”,则直接返回 path;如果 path 以“/"结尾,则对 path 后追加“”;其他 情 况 下 , path 后 面 追 加 关 于 dispatcherServletRegistration 方 法 中 对DispatcherServletRegistrationBean 其他属性的简单赋值操作就不再赘述了。

我们再回到针对 DispatcherServletRegistrationConfiguration 的注解@Conditional 指定的限定条件类 DispatcherServletRegistrationCondition,该限定条件类主要用来判断进行dis-patcherServlet 和 dispatcherServletRegistration 是否存在等。

DispatcherServletRegistrationCondition 中判断条件及代码较多,这里只展示顶层判断方法的源代码。

@Order (Ordered. LOWEST_ PRECEDENCE - 10)

private static class DispatcherServletRegistrationCondition extends SpringBoot-Condition

{

@Override

public ConditionOutcome getMatchOutcome(ConditionContext context, Annotated-

TypeMetadata metadata) {

ConfigurableListableBeanFactory beanFactory = context . getBeanFactory();

//判断是否重复存在 dispatcherServlet

ConditionOutcome outcome = checkDefaultDispatcherName (beanFactory);

if (!outcome . isMatch()) {

return outcome ;//判断是否重复存在 dispatcherServletRegistration

return checkServletRegistration(beanFactory);

}

DispatcherServletRegistrationCondition 的 getMatchOutcome 方 法 中 分 别 判 断 了dispatcherServlet 和 dispatcherServletRegistration 是否已经存在对应的 Bean。具体判断方法基本与上节讲到的 DefaultDispatcherServletCondition 的判断方法一致。

至此,在该自动配置类中,DispatcherServlet 的创建、 简单初始化和注册已经完成。当第一次接收到网络请求时,DispatcherServlet 内部会进行一 系列的初始化操作,这些更多属于 Spring 的内容,就不再展开了。

SpringBootWeb应用源码解析:遗失的web.xmI


本文给大家讲解的内容是SpringBootWeb应用源码解析:遗失的web.xmI和Web应用的自动配置


  1. 下篇文章给大家讲解的是SpringBootWeb应用源码解析:Spring MVC 的自动配置;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

推荐阅读
  • 本文探讨了如何通过一系列技术手段提升Spring Boot项目的并发处理能力,解决生产环境中因慢请求导致的系统性能下降问题。 ... [详细]
  • 解决FCKeditor应用主题后上传问题及优化配置
    本文介绍了在Freetextbox收费后选择FCKeditor作为替代方案时遇到的上传问题及其解决方案。通过调整配置文件和调试工具,最终解决了上传失败的问题,并对相关配置进行了优化。 ... [详细]
  • 深入理解Vue.js:从入门到精通
    本文详细介绍了Vue.js的基础知识、安装方法、核心概念及实战案例,帮助开发者全面掌握这一流行的前端框架。 ... [详细]
  • 本文详细介绍了如何在云服务器上配置Nginx、Tomcat、JDK和MySQL。涵盖从下载、安装到配置的完整步骤,帮助读者快速搭建Java Web开发环境。 ... [详细]
  • 本文深入探讨了HTTP请求和响应对象的使用,详细介绍了如何通过响应对象向客户端发送数据、处理中文乱码问题以及常见的HTTP状态码。此外,还涵盖了文件下载、请求重定向、请求转发等高级功能。 ... [详细]
  • 本文详细探讨了HTML表单中GET和POST请求的区别,包括它们的工作原理、数据传输方式、安全性及适用场景。同时,通过实例展示了如何在Servlet中处理这两种请求。 ... [详细]
  • 使用lambda表达式排序Collections.sort(temp,(Stringa,Stringb)-{returnb.compareTo(a);});Collections ... [详细]
  • JavaScript 基础语法指南
    本文详细介绍了 JavaScript 的基础语法,包括变量、数据类型、运算符、语句和函数等内容,旨在为初学者提供全面的入门指导。 ... [详细]
  • 2018-2019学年第六周《Java数据结构与算法》学习总结
    本文总结了2018-2019学年第六周在《Java数据结构与算法》课程中的学习内容,重点介绍了非线性数据结构——树的相关知识及其应用。 ... [详细]
  • JavaScript 中创建对象的多种方法
    本文详细介绍了 JavaScript 中创建对象的几种常见方式,包括对象字面量、构造函数和 Object.create 方法,并提供了示例代码和属性描述符的解释。 ... [详细]
  • 我有一个SpringRestController,它处理API调用的版本1。继承在SpringRestControllerpackagerest.v1;RestCon ... [详细]
  • 简化报表生成:EasyReport工具的全面解析
    本文详细介绍了EasyReport,一个易于使用的开源Web报表工具。该工具支持Hadoop、HBase及多种关系型数据库,能够将SQL查询结果转换为HTML表格,并提供Excel导出、图表显示和表头冻结等功能。 ... [详细]
  • Redux入门指南
    本文介绍Redux的基本概念和工作原理,帮助初学者理解如何使用Redux管理应用程序的状态。Redux是一个用于JavaScript应用的状态管理库,特别适用于React项目。 ... [详细]
  • docker镜像重启_docker怎么启动镜像dock ... [详细]
  • 本文将详细介绍通过CAS(Central Authentication Service)实现单点登录的原理和步骤。CAS由耶鲁大学开发,旨在为多应用系统提供统一的身份认证服务。文中不仅涵盖了CAS的基本架构,还提供了具体的配置实例,帮助读者更好地理解和应用这一技术。 ... [详细]
author-avatar
mobiledu2502898013
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有