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

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

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 <? e

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

eption;

public ServletRegistration getServletRegistration(String servletName);

public Map<String, ? extends ServletRegistration> 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 应用的自动配置的。

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<String> 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 {}


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<MultipartConfigElement> 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<DispatcherServlet> 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<String> 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和Web应用的自动配置

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值