1. 为什么要写web.xml方式配置Servlet:很多项目至今还在用
先回答一个很多人刚入行时的疑问:现在用IDE新建一个Servlet,不是自动生成@WebServlet注解就完事了吗,为什么还要去手写web.xml?
这个问题的答案,我在实际开发里体会太深了。先说结论:除非你确定自己永远只做新项目、只用最新框架,否则web.xml这套配置能力必须掌握。我见过好几个维护了七八年的老系统,控制层清一色是web.xml里写死的Servlet映射,后来接手的人因为只会注解方式,连加一个接口都要折腾半天。
再往深处说,web.xml方式其实承载着Servlet规范里最本质的设计思路:把"请求的URL"和"处理请求的Java类"之间的对应关系,交给容器去管理。 这种声明式配置的好处是,代码里不用硬编码路由,要调整路径映射、要加初始化参数、要控制加载顺序,全部改配置文件就行,Java类本身保持纯净。
另外还有一个很实际的原因:学习阶段用web.xml方式,你对Servlet的生命周期、URL匹配规则、容器加载机制的理解会扎实得多。注解方式把这些细节都藏起来了,出了问题反而不知道从哪里排查。这篇文章我就以Tomcat环境为基准,从零到一写一个完整的、能直接在Tomcat里跑起来的Servlet项目,配置全部走web.xml方式,顺便把里面涉及的关键点都拆开讲明白。
注意,这里的web.xml指的是WEB-INF/web.xml,它是Java Web应用的部署描述符。Servlet容器启动时会读取这个文件,根据里面的配置决定创建哪些Servlet实例、如何映射请求路径、加载哪些监听器或过滤器。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动手前必须搞懂的Servlet生命周期与映射原理
2.1 Servlet的一生:从加载到销毁
Servlet本质上是一个运行在容器里的Java类,它自己没有main方法,什么时候创建、什么时候销毁,都由容器说了算。整个生命周期围绕三个核心方法展开:init()、service()、destroy()。
流程是这样的:
- 容器启动时(或第一次收到请求时)根据web.xml中的配置,通过反射创建Servlet实例。
- 紧接着调用
init(ServletConfig)方法做初始化,这个方法在整个生命周期里只调用一次。所以那些需要加载一次的配置项、需要建立的数据库连接池,都放在init里做。 - 每当容器接收到匹配当前Servlet映射路径的请求,会调用
service(HttpServletRequest, HttpServletResponse)方法。service内部再根据HTTP方法类型(GET、POST等)分发给doGet或doPost处理。 - 容器关闭或应用重新加载时,调用
destroy()方法,释放资源。
有一个容易让人迷惑的点:Tomcat默认使用的是"第一次请求时才实例化",也就是懒加载模式。但如果你希望容器启动时就完成某个Servlet的初始化,后面会讲到web.xml里的load-on-startup配置。
2.2 URL怎么找到Servlet:匹配规则一定要吃透
Web应用收到请求后,容器拿着请求的URL去和web.xml中所有<servlet-mapping>里的<url-pattern>比对,命中哪个就交给对应的Servlet处理。
URL匹配规则要记牢,这直接决定了你在web.xml里怎么配置:
| 规则类型 | 写法示例 | 匹配说明 |
|---|---|---|
| 精确匹配 | /hello |
只有请求路径完全等于/hello才命中 |
| 路径匹配 | /api/* |
所有以/api/开头的路径都命中,比如/api/user/list |
| 扩展名匹配 | *.do |
所有以.do结尾的URL都命中,比如/user/add.do |
| 默认匹配 | / |
兜底Servlet,匹配所有未被其他映射处理的请求 |
这里要特别注意一个常见的坑:/*和/看起来很像,实际行为天差地别。/*会匹配所有请求,包括JSP页面和对静态资源的请求;而/只匹配那些没有其他映射可走的请求。如果你写了/*,很可能会把容器内部的默认Servlet给拦截掉,导致页面渲染异常。
另外,容器在做匹配时,遵循"最长路径优先"的原则。也就是说,如果同一个URL同时匹配了/api/*和精确路径/api/login,精确路径的优先级更高。了解这个原则,在配置多个Servlet时就不容易打架。
2.3 ServletConfig与ServletContext:两个容易混淆的对象
ServletConfig:当前这个Servlet的配置信息,包括Servlet名称、初始化参数(web.xml里的<init-param>)。每个Servlet都有自己的ServletConfig。ServletContext:整个Web应用的全局上下文,所有Servlet共享同一个实例。可以在里面存全局配置或共享数据,比如读取web.xml里的<context-param>。
区分这两个对象很重要。把"只属于某个Servlet的配置"写在<init-param>里,把"整个应用通用的配置"写在<context-param>里,这样代码的可维护性会好很多。
3. 从零搭建一个web.xml方式Servlet的完整示例
3.1 项目结构规划
这里我以一个简单的用户登录模拟功能为例,展示完整流程。这个例子虽然业务逻辑简单,但把web.xml方式的各个关键配置都串起来了。项目采用标准的Maven目录结构。
code复制servlet-webxml-demo
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── demo
│ └── servlet
│ ├── LoginServlet.java
│ ├── UserListServlet.java
│ └── InitDataListener.java
└── webapp
├── index.jsp
├── login.jsp
├── WEB-INF
│ ├── web.xml
│ └── lib
└── static
├── css
└── js
如果你还在用传统的Eclipse Dynamic Web Project结构,没关系,核心不变——Java源码放src目录,web.xml必须放在webapp/WEB-INF/或WebContent/WEB-INF/下,不能放错位置,否则容器根本找不到这个配置文件。
3.2 编写第一个Servlet类并建立web.xml映射
先写一个最简单的UserListServlet,负责返回用户列表JSON数
