Servlet 是 Java Web 核心組件,`web.xml` 中的 `
<web-app xmlns="https://xmlns.jcp.org/xml/ns/javaee"
version="3.1">
<!-- 1. Servlet 定義:名稱 + 完全限定類名 -->
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.example.servlet.MyServlet</servlet-class>
</servlet>
<!-- 2. 映射規則:Servlet 名稱 + URL 模式 -->
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/api/users</url-pattern>
</servlet-mapping>
</web-app>
| 元素 | 子元素 | 作用 | 關鍵點 |
|---|---|---|---|
| <servlet> | <servlet-name> | 邏輯名稱,供 mapping 引用 | 自定義,建議有語義 |
| <servlet-class> | 完全限定類名 | 必須實現 Servlet 接口/繼承 HttpServlet | |
| <init-param> | 初始化參數 | ServletConfig.getInitParameter() 獲取 | |
| <load-on-startup> | 啟動加載順序 | ≥0 整數,數值小先加載 | |
| <servlet-mapping> | <servlet-name> | 引用 servlet 定義 | 必須與定義一致 |
| <url-pattern> | URL 匹配規則 | 核心路由邏輯,見下文 |
| 類型 | 語法 | 示例 | 匹配示例 | 用途 |
|---|---|---|---|---|
| 精確匹配 | 以 `/` 開頭,無通配符 | `/api/users` | 僅 `/api/users` | REST 端點、固定路徑 |
| 路徑匹配 | 以 `/` 開頭,以 `/*` 結尾 | `/api/*`、`/*` | `/api/users`、`/api/orders/123`、`/any/path` | 前綴路由、前端控制器 |
| 擴展匹配 | 以 `*.` 開頭 | `*.do`、`*.action`、`*.json` | `submit.do`、`/path/to/action.do` | 傳統框架、靜態資源區分 |
當請求進入容器,按以下順序查找,**首個匹配生效**:
<servlet-mapping>
<servlet-name>ServletA</servlet-name>
<url-pattern>/test/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ServletB</servlet-name>
<url-pattern>/test/b/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ServletC</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
| 請求路徑 | 匹配結果 | 原因 |
|----------|----------|------|
| `/test/b/c` | **ServletB** | 最長路徑匹配(`/test/b/` > `/test/`) |
| `/test/a` | **ServletA** | 路徑匹配 `/test/*` |
| `/test/b.action` | **ServletC** | 擴展匹配 `*.do` 不匹配,無 `.do` 後綴 → 404 或默認 Servlet |
| `/test/b.do` | **ServletC** | 擴展匹配 `*.do` 生效(路徑匹配優先級高於擴展,但 `/test/b.do` 不以 `/test/` 開頭?實際:`/test/b.do` 以 `/test/` 開頭,**路徑匹配優先** → ServletA) |
> **關鍵結論**:路徑匹配優先級**高於**擴展匹配。`/test/b.do` 會匹配 `/test/*`(ServletA),而非 `*.do`(ServletC)。這是 Servlet 規範設計為「目錄結構優於文件類型」。
| 錯誤 | 現象 | 修正 |
|---|---|---|
| `servlet-name` 不一致 | 404、ClassNotFound、映射失效 | 定義與映射名稱完全一致 |
| `url-pattern` 寫成 `*.action` 但請求帶路徑 | `/user/save.action` 匹配 `*.action` 正常,但 `/user/save` 不匹配 | 按需選擇路徑匹配或擴展匹配 |
| 同一 Servlet 多 mapping 衝突 | 不可預期行為 | 一個 Servlet 對應一個 mapping,複雜路由用 Filter/前端控制器 |
| 忽略 `load-on-startup` | 首次請求慢(初始化延遲) | 核心 Servlet 設定 `1` 或更小 |
| Servlet 3.0+ 註解與 web.xml 混用衝突 | 重複註冊、覆蓋 | 二選一:傳統項目用 web.xml,現代項目用 `@WebServlet` |
@WebServlet(
name = "MyServlet",
urlPatterns = { "/api/users", "/api/users/*" },
initParams = {
@WebInitParam(name = "pageSize", value = "20"),
@WebInitParam(name = "encoding", value = "UTF-8")
},
loadOnStartup = 1,
asyncSupported = true // 支援異步處理
)
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 業務邏輯
}
}
Spring Boot 不使用 `web.xml`,提供三種註冊方式:
@Bean
public ServletRegistrationBean myServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean<>(new MyServlet(), "/api/users/*");
bean.setLoadOnStartup(1);
bean.addInitParameter("encoding", "UTF-8");
return bean;
}
理解 Servlet 映射機制,是排查 404、請求不到 Controller、靜態資源攔截等問題的關鍵。從 `web.xml` 到註解再到 Spring Boot 自動配置,本質邏輯不變:**名稱綁定類、模式匹配路徑、優先級決定勝負**。