百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

深入Spring Boot (十五):web.xml去哪了

xsobi 2024-11-24 23:34 1 浏览

如今,开发基于Spring的web应用越来越少使用到web.xml,或者基本上已经看不到web.xml,那这个web.xml到底去哪了呢,接下来我们一起来探索一下。

Servlet3前使用web.xml

在Servlet3.0之前,web.xml是开发web应用必须配置的文件,可以通过它配置DispatcherServlet、ContextLoaderListener和其它额外的Servlet、Filter、Listener,就像如下的web.xml配置。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<display-name>web-app</display-name>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath*:spring/*.xml</param-value>
	</context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

	<servlet>
		<servlet-name>springMVC</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath*:springMVC.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>

以上的web.xml在应用启动的时候会创建两个Spring上下文,一个由ContextLoaderListener创建的上下文,一个由DispatcherServlet创建的上下文。ContextLoaderListener创建的上下文用于装载非web功能相关的bean,例如Service、DAO等,而DispatcherServlet创建的上下文用于装载web功能相关的bean,例如Controller、ViewResolver等。ContextLoaderListener创建的上下文要装载的bean来自于web.xml中通过context-param标签配置的contextConfigLocation指定的xml,例如classpath:spring/.xml;而DispatcherServlet创建的上下文要装载的bean来自于web.xml中配置的DispatcherServlet中通过init-param标签配置的contextConfigLocation指定的xml,例如classpath*:springMVC.xml,如果没有通过init-param标签配置contextConfigLocation,默认使用以DispatcherServlet在web.xml中配置的servlet-name为前缀,-servlet.xml为后缀的xml文件,例如springMVC-servlet.xml。

Servlet3+弱化web.xml

Servlet3.0在Servlet2.5的基础上提供了若干新特性用于简化Web应用的开发和部署,在servlet-api.jar的javax.servlet.annotation包中新增了@WebServlet、@WebFilter和@WebListener注解,用于简化Servlet、过滤器和监听器的声明,也就是说从此之后开发web应用不一定非要使用web.xml了,例如如下代码声明了一个自定义Filter。

在Servlet3.0 API中提供了一个javax.servlet.ServletContainerInitializer接口,接口只有一个onStartup方法,在支持Servlet3.0的Web应用服务器中,例如Tomcat7或更高版本,服务器会在启动的时候在类路径下查找javax.servlet.ServletContainerInitializer接口的实现类,执行实现类的onStartup方法用于配置Servlet容器,例如注册Servlet、Filter或Listener。

onStartup方法有两个参数:Set<Class<?>> c和ServletContext ctx,ServletContext即Servlet上下文,它定义了一些方法用于和Servlet容器进行交流,也可以获取web应用的一些资源信息;如果ServletContainerInitializer接口的实现类使用@HandlesTypes注解声明了感兴趣的类或接口,那么这个感兴趣的类及其子类或接口的实现类就会被设置到Set<Class<?>> c中。

ServletContainerInitializer接口的具体使用方法必须在代码的classpath下的META-INF/services/路径下定义一个名为javax.servlet.ServletContainerInitializer的文件,这个文件的内容是ServletContainerInitializer接口实现类的全路径,例如com.example.demo.MyServletContainerInitializer,下面实现一个简单的MyServletContainerInitializer。

从上图可以看到,ServletContext提供了可以注册Servlet、Filter和Listener的方法,下面动态注册一个Servlet和Filter。

Servlet3.0新增的这些特性在弱化web.xml,下面来看一下Spring是如何支持Servlet3的。

Spring3+逐渐替换web.xml

Spring框架从3.1版本开始支持Servlet3.0,可以在基于Java的配置中声明Servlet、Filter和Listener,并且从3.2版本开始可以使用AbstractAnnotationConfigDispatcherServletInitializer的子类来配置DispatcherServlet,它会创建DispatcherServlet和ContextLoaderListener,真正实现不再需要使用web.xml,例如如下代码自定义了一个DispatcherServletInitializer,它继承了AbstractAnnotationConfigDispatcherServletInitializer。

DispatcherServletInitializer分别实现了getRootConfigClasses、getServletConfigClasses和getServletMappings方法。getRootConfigClasses方法返回使用@Configuration标注的类用于ContextLoaderListener创建Spring上下文装载非web相关的bean。getServletConfigClasses方法返回使用@Configuration标注的类用于DispatcherServlet创建Spring上下文装载web相关的bean。getServletMappings方法返回的字符串数组用于告诉DispatcherServlet处理那些url的请求。到这里这三个方法的用途是不是很熟悉,其实就是web.xml中配置的DispatcherServlet和ContextLoaderListener替代方案。

Spring3.1中的SpringServletContainerInitializer实现了ServletContainerInitializer接口,同时在相应的jar包META-INF/services/路径下定义了javax.servlet.ServletContainerInitializer文件,它的内容是org.springframework.web.SpringServletContainerInitializer。

根据上面对javax.servlet.ServletContainerInitializer接口分析可知,SpringServletContainerInitializer的onStartup方法会在web应用启动的时候被调用。在分析onStartup方法之前,关注到SpringServletContainerInitializer类上使用@HandlesTypes注解标注,这个注解的value是WebApplicationInitializer,所以,支持Servlet3.0+的容器在启动时会自动扫描classpath下WebApplicationInitializer接口的实现类,并将这些实现类传递给onStartup方法的第一个参数。

上面代码做了简单的注释,可以看到onStartup会遍历执行WebApplicationInitializer接口实现类的onStartup方法。现在,我们在回过来看一下AbstractAnnotationConfigDispatcherServletInitializer类结构。

AbstractAnnotationConfigDispatcherServletInitializer继承自AbstractDispatcherServletInitializer,而AbstractDispatcherServletInitializer又继承自AbstractContextLoaderInitializer,AbstractContextLoaderInitializer实现了WebApplicationInitializer接口,所以AbstractAnnotationConfigDispatcherServletInitializer子类的onStartup方法会在web应用启动的时候被调用,创建DispatcherServlet和ContextLoaderListener,进而创建Spring上下文,完成应用初始化操作。

SpringBoot不再使用web.xml

既然Spring框架从3.1开始逐步使用Java Config替换web.xml,那么SpringBoot作为快速、简便使用Spring框架的脚手架,必然也不会再继续使用web.xml了。

在基于SpringBoot开发的代码中依然可以继续使用servlet-api中javax.servlet.annotation包中新增的@WebServlet、@WebFilter和@WebListener注解,用于简化Servlet、过滤器和监听器的声明,不过需要注意别忘记使用SpringBoot提供的@ServletComponentScan注解开启对这三注解的扫描。

SpringBoot提供了ServletRegistrationBean、FilterRegistrationBean和ServletListenerRegistrationBean,用于动态注册Servlet、过滤器和监听器,例如如下代码。

上面代码虽然创建了三个bean,但这三个bean仅托管到了Spring上下文中,并没有注册到ServletContext中,那什么时候被注册到ServletContext中呢?查看ServletRegistrationBean、FilterRegistrationBean和ServletListenerRegistrationBean源码会发现,它们都间接继承自RegistrationBean,而RegistrationBean实现了ServletContextInitializer接口。

注意看RegistrationBean实现的是ServletContextInitializer接口,它是SpringBoot提供的接口,和我们上面说到的ServletContainerInitializer接口不是同一个,一定不要混淆。ServletContextInitializer接口使用编程的方式配置Servlet3.0+的ServletContext,它不会被Servlet容器启动时自动调用,它的生命周期由Spring管理。

下面以代码运行在Tomcat7+的版本为例,当SpringBoot项目代码运行的时候,无论是内嵌Tomcat还是将代码打成war部署到外部Tomcat,代码都会运行到SpringApplication.run方法,创建Spring上下文装载bean,做初始化操作,在这一过程中会创建一个TomcatStarter对象,然后会执行TomcatStarter中的onStartup方法,下面是TomcatStarter源码。

通过源码发现TomcatStarter也实现了ServletContainerInitializer接口,不过它没有使用和SpringServletContainerInitializer一样的实现机制,而是采用硬编码的方式,直接new了一个TomcatStarter,在创建TomcatStarter对象的时候,传入了一个ServletContextInitializer数组,这个数组里的内容是从Spring上下文搜索到的ServletContextInitializer接口实现类的bean,也就是说我们上面通过ServletRegistrationBean、FilterRegistrationBean和ServletListenerRegistrationBean创建的bean(myServlet、myFilter和myListener)都会注入到这个数组中,其实这个数组里面还有一个很重要的bean,就是dispatcherServlet,它是由SpringBoot自动配置功能通过DispatcherServletRegistrationBean创建的bean。

TomcatStarter对象创建完成后,在接下来的初始化过程中会回调它的onStartup方法,在这个方法的内部可以看到,它依然是执行了各个ServletContextInitializer接口实现类的onStartup,进而将Servlet、Filter和Listener注册到ServletContext。

总结

至此,我们已经了解了web.xml是如何被替换的,我们也发现框架封装的东西越来越多,集成度也越来越高,框架虽好,如果我们不了解来龙去脉,只做一个工具的使用者,时间久了,我们也就是一个工具人,所以,研究一下why、what和how很有必要。

学之多,而后知之少!朋友们【点赞+评论+转发】是我持续更新的最大动力,我们下期见!

往期推荐

深入Spring Boot (十四):打包解决方案

聊一聊Redis官方置顶推荐的Java客户端Redisson

面试官一步一步的套路你,为什么SimpleDateFormat不是线程安全的

超实用高并发编程ExecutorCompletionService案例分析与源码解读

都说ThreadLocal被面试官问烂了,可为什么面试官还是喜欢继续问

Spring声明式事务处理的实现原理,来自面试官的穷追拷问

Spring框架你敢写精通,面试官就敢问@Autowired注解的实现原理

面试被问为什么使用Spring Boot?答案好像没那么简单

深入理解Spring之十:SpringMVC请求分发解析

Spring MVC相关的面试题就是无底洞,反正我是怕了

相关推荐

js向对象中添加元素(对象,数组) js对象里面添加元素

一、添加一个元素对象名["属性名"]=值(值:可以是一个值,可以是一个对象,也可以是一个数组)这样添加进去的元素,就是一个值或对象或数组...

JS小技巧,如何去重对象数组?(一)

大家好,关于数组对象去重的业务场景,想必大家都遇到过类似的需求吧,这对这样的需求你是怎么做的呢。下面我就先和大家分享下如果是基于对象的1个属性是怎么去重实现的。方法一:使用.filter()和....

「C/C++」之数组、vector对象和array对象的比较

数组学习过C语言的,对数组应该都不会陌生,于是这里就不再对数组进行展开介绍。模板类vector模板类vector类似于string,也是一种动态数组。能够在运行阶段设置vector对象的长度,可以在末...

如何用sessionStorage保存对象和数组

背景:在工作中,我将[{},{}]对象数组形式,存储到sessionStorage,然后ta变成了我看不懂的形式,然后我想取之用之,发现不可能了~记录这次深刻的教训。$clickCouponIndex...

JavaScript Array 对象 javascript的array对象

Array对象Array对象用于在变量中存储多个值:varcars=["Saab","Volvo","BMW"];第一个数组元素的索引值为0,第二个索引值为1,以此类推。更多有...

JavaScript中的数组Array(对象) js array数组

1:数组Array:-数组也是一个对象-数组也是用来存储数据的-和object不同,数组中可以存储一组有序的数据,-数组中存储的数据我们称其为元素(element)-数组中的每一个元素都有一...

数组和对象方法&amp;数组去重 数组去重的5种方法前端

列举一下JavaScript数组和对象有哪些原生方法?数组:arr.concat(arr1,arr2,arrn);--合并两个或多个数组。此方法不会修改原有数组,而是返回一个新数组...

C++ 类如何定义对象数组?初始化数组?linux C++第43讲

对象数组学过C语言的读者对数组的概念应该很熟悉了。数组的元素可以是int类型的变量,例如int...

ElasticSearch第六篇:复合数据类型-数组,对象

在ElasticSearch中,使用JSON结构来存储数据,一个Key/Value对是JSON的一个字段,而Value可以是基础数据类型,也可以是数组,文档(也叫对象),或文档数组,因此,每个JSON...

第58条:区分数组对象和类数组对象

示例设想有两个不同类的API。第一个是位向量:有序的位集合varbits=newBitVector;bits.enable(4);bits.enable([1,3,8,17]);b...

八皇后问题解法(Common Lisp实现)

如何才能在一张国际象棋的棋盘上摆上八个皇后而不致使她们互相威胁呢?这个著名的问题可以方便地通过一种树搜索方法来解决。首先,我们需要写一个函数来判断棋盘上的两个皇后是否互相威协。在国际象棋中,皇后可以沿...

visual lisp修改颜色的模板函数 怎么更改visual studio的配色

(defunBF-yansemokuai(tuyuanyanse/ss)...

用中望CAD加载LISP程序技巧 中望cad2015怎么加载燕秀

1、首先请加载lisp程序,加载方法如下:在菜单栏选择工具——加载应用程序——添加,选择lisp程序然后加载,然后选择添加到启动组。2、然后是添加自定义栏以及图标,方法如下(以...

图的深度优先搜索和广度优先搜索(Common Lisp实现)

为了便于描述,本文中的图指的是下图所示的无向图。搜索指:搜索从S到F的一条路径。若存在,则以表的形式返回路径;若不存在,则返回nil。...

两个有助于理解Common Lisp宏的例子

在Lisp中,函数和数据具有相同的形式。这是Lisp语言的一个重大特色。一个Lisp函数可以分析另一个Lisp函数;甚至可以和另一个Lisp函数组成一个整体,并加以利用。Lisp的宏,是实现上述特色的...