Skip to content

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 种配置方式:

  1. 完全精确匹配:以 / 开头的明确路径,如 /abc/user/login
  2. 目录/路径模糊匹配:以 / 开头,并以 /* 结尾,如 /abc/*/user/*
  3. 扩展名/后缀模糊匹配:以 *. 开头,后接拓展名,如 *.do*.action。(注意:前缀不能带 /)。
  4. 缺省/默认匹配:单斜杠 //*,匹配所有未被捕获的请求。

匹配优先级法则

规则完全精确匹配 > 目录模糊匹配 > 扩展名模糊匹配 > 缺省匹配

实践测试案例:

假设服务器同时注册了以下 4 个 Servlet 映射:

  • Servlet1: /abc/*
  • Servlet2: /*
  • Servlet3: /abc
  • Servlet4: *.do
客户端请求地址匹配命中的 Servlet原因分析
http://localhost/abcServlet3满足精确匹配 /abc,优先级最高
http://localhost/abc/a.htmlServlet1匹配目录模糊 /abc/*,优先于 /**.do
http://localhost/a.doServlet2匹配目录 /*,目录匹配优先级高于扩展名匹配 *.do
http://localhost/xxxx/yyy/a.doServlet2匹配目录 /* 优先

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);
    }
}

基于 VitePress 构建 | 技术知识库