Appearance
Servlet 进阶机制与 URL 映射规则
本文深入剖析 Servlet 在 Tomcat 中的底层访问执行流程、
<load-on-startup>启动加载控制、URL 路径匹配优先级、单例多线程并发安全及 ServletConfig 配置对象。
1. Servlet 在 Tomcat 中的执行流程
当客户端浏览器向服务器发送一个请求(例如 http://localhost:8080/app/demo1)时,Tomcat 内部的执行步骤如下:
[ 浏览器请求 /app/demo1 ]
↓
[ 1. 解析 Host & Context ] ──→ 找到对应的 Web 项目(Context)
↓
[ 2. 匹配 web.xml ] ──→ 查找 <url-pattern>/demo1</url-pattern>,获取对应的 <servlet-name>
↓
[ 3. 得到 servlet-class ] ──→ 根据 <servlet-name> 查找对应的全限定类名(如 com.example.DemoServlet)
↓
[ 4. 检查与实例化 ] ──→ 内存中若不存在该 Servlet 实例:
① 通过反射 `Class.forName()` 加载字节码并实例化
② 调用 `init(ServletConfig)` 方法进行初始化
↓
[ 5. 线程处理与响应 ] ──→ 开启/复用工作线程,调用 `service(request, response)` 方法提供服务2. 修改 Servlet 加载与初始化时机
默认情况下,Servlet 采用延迟加载策略——即在第一次收到客户端请求时才会被实例化与初始化。
如果 Servlet 的初始化需要执行较重的操作(如加载大型配置文件、建立数据库连接池),第一次访问可能导致明显卡顿。此时可以通过 <load-on-startup> 配置改变其加载时机:
xml
<servlet>
<servlet-name>demoServlet</servlet-name>
<servlet-class>net.wanho.demo.DemoServlet</servlet-class>
<!--
配置 load-on-startup 值为正整数 (如 1, 2, 3...)
表示该 Servlet 会在 Tomcat 服务器启动时立即进行实例化与 init() 初始化。
数字越小,启动时的初始化优先级越高。
-->
<load-on-startup>1</load-on-startup>
</servlet>3. URL 映射路径 (url-pattern) 匹配优先级
在 web.xml 或 @WebServlet 中配置 url-pattern 时,主要有以下 4 种配置方式:
- 完全精确匹配:以
/开头的明确路径,如/abc、/user/login。 - 目录/路径模糊匹配:以
/开头,并以/*结尾,如/abc/*、/user/*。 - 扩展名/后缀模糊匹配:以
*.开头,后接拓展名,如*.do、*.action。(注意:前缀不能带/)。 - 缺省/默认匹配:单斜杠
/或/*,匹配所有未被捕获的请求。
匹配优先级法则
规则:完全精确匹配 > 目录模糊匹配 > 扩展名模糊匹配 > 缺省匹配
实践测试案例:
假设服务器同时注册了以下 4 个 Servlet 映射:
Servlet1:/abc/*Servlet2:/*Servlet3:/abcServlet4:*.do
| 客户端请求地址 | 匹配命中的 Servlet | 原因分析 |
|---|---|---|
http://localhost/abc | Servlet3 | 满足精确匹配 /abc,优先级最高 |
http://localhost/abc/a.html | Servlet1 | 匹配目录模糊 /abc/*,优先于 /* 与 *.do |
http://localhost/a.do | Servlet2 | 匹配目录 /*,目录匹配优先级高于扩展名匹配 *.do |
http://localhost/xxxx/yyy/a.do | Servlet2 | 匹配目录 /* 优先 |
4. Servlet 多线程并发安全指南
- 单例模式:在默认情况下,整个 Web 应用中每个 Servlet 类仅存在一个共享的实例对象。
- 多线程并发:Tomcat 会为每个并发的 HTTP 请求分配/从线程池开辟一个工作线程来调用
service()方法。 - 安全隐患:如果多个线程同时访问并修改 Servlet 实例中的共享成员变量(全局属性),极易引发严重的数据竞态与并发安全问题。
java
// ❌ 存在并发安全风险的写法
public class UnsafeServlet extends HttpServlet {
private int count = 0; // 成员变量:多线程共享!
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
count++; // 线程不安全操作
}
}
// ✅ 安全最佳实践:
// 1. 尽量不要在 Servlet 中定义可变的成员变量(保持无状态设计)
// 2. 将数据定义为局部变量(方法内栈变量),每个线程拥有独立栈帧5. ServletConfig 配置对象
ServletConfig 是 Servlet 的配置参数对象,在 Servlet 实例化后由 Tomcat 在调用 init(ServletConfig config) 时注入。
主要用于获取特定 Servlet 在 web.xml 中配置的私有初始化参数:
xml
<servlet>
<servlet-name>configServlet</servlet-name>
<servlet-class>net.wanho.demo.ConfigServlet</servlet-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</servlet>java
public class ConfigServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
// 获取单个私有初始化参数
String encoding = config.getInitParameter("encoding");
System.out.println("Servlet 初始化编码: " + encoding);
}
}