Servlet 3.0 作為 Java EE 6 核心規格,引入三大革命性特性:**非同步處理** 解決執行緒阻塞、**註解配置** 消除 web.xml、**可插拔架構** 實現模組化擴充。本文深度解析每項特性的原理、配置與實戰應用。
請求 → Servlet 執行緒 → 業務邏輯 (阻塞) → 響應 → 執行緒釋放 問題:高併發時執行緒池耗盡,新請求排隊/拒絕
請求 → Servlet 執行緒 → 啟動非同步上下文 → 執行緒立即返回容器
↓
非同步執行緒 → 業務邏輯 → 直接響應或轉發
↓
complete() 結束非同步處理
| 配置方式 | 設定 |
|---|---|
| web.xml | <async-supported>true</async-supported> 在 <servlet>/<filter> 內 |
| @WebServlet | @WebServlet(urlPatterns="/async", asyncSupported=true) |
| @WebFilter | @WebFilter(urlPatterns="/async", asyncSupported=true) |
@WebServlet(urlPatterns = "/demo", asyncSupported = true)
public class AsyncDemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("進入 Servlet 時間:" + new Date() + ".
");
out.flush();
// 1. 啟動非同步上下文
AsyncContext ctx = req.startAsync();
// 2. 交給非同步執行緒處理
new Thread(new Executor(ctx)).start();
// 3. 主執行緒立即返回
out.println("結束 Servlet 時間:" + new Date() + ".
");
out.flush();
}
}
class Executor implements Runnable {
private AsyncContext ctx;
public Executor(AsyncContext ctx) { this.ctx = ctx; }
@Override
public void run() {
try {
Thread.sleep(10000); // 模擬 10 秒耗時業務
PrintWriter out = ctx.getResponse().getWriter();
out.println("業務處理完畢時間:" + new Date() + ".
");
out.flush();
ctx.complete(); // 關鍵:通知容器非同步處理結束
} catch (Exception e) { e.printStackTrace(); }
}
}
AsyncContext ctx = req.startAsync();
ctx.addListener(new AsyncListener() {
@Override public void onComplete(AsyncEvent event) throws IOException {
// 清理資源、記錄日誌
}
@Override public void onTimeout(AsyncEvent event) throws IOException { }
@Override public void onError(AsyncEvent event) throws IOException { }
@Override public void onStartAsync(AsyncEvent event) throws IOException { }
});
| 註解 | 對應 web.xml 元素 | 關鍵屬性 |
|---|---|---|
@WebServlet | <servlet> + <servlet-mapping> | urlPatterns、name、loadOnStartup、initParams、asyncSupported |
@WebFilter | <filter> + <filter-mapping> | urlPatterns/servletNames、filterName、dispatcherTypes、asyncSupported |
@WebListener | <listener> | value (描述) |
@WebInitParam | <init-param> | name、value、description |
@MultipartConfig | 無 (新增檔案上傳支援) | location、maxFileSize、maxRequestSize、fileSizeThreshold |
@WebServlet(
urlPatterns = {"/simple"},
asyncSupported = true,
loadOnStartup = -1,
name = "SimpleServlet",
displayName = "ss",
initParams = @WebInitParam(name = "username", value = "tom")
)
public class SimpleServlet extends HttpServlet { ... }
<servlet>
<display-name>ss</display-name>
<servlet-name>SimpleServlet</servlet-name>
<servlet-class>footmark.servlet.SimpleServlet</servlet-class>
<load-on-startup>-1</load-on-startup>
<async-supported>true</async-supported>
<init-param>
<param-name>username</param-name>
<param-value>tom</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>SimpleServlet</servlet-name>
<url-pattern>/simple</url-pattern>
</servlet-mapping>
<web-app metadata-complete="true"> <!-- 忽略所有註解、web-fragment.xml -->
<web-app metadata-complete="false"> <!-- 預設:啟用註解掃描與可插拔 -->
META-INF/ 目錄,內容結構與 web.xml 幾乎相同WEB-INF/lib/,容器啟動自動掃描合併<?xml version="1.0" encoding="UTF-8"?>
<web-fragment
xmlns="https://java.sun.com/xml/ns/javaee"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
version="3.0"
xsi:schemaLocation="https://java.sun.com/xml/ns/javaee
https://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd"
metadata-complete="true">
<servlet>
<servlet-name>fragment</servlet-name>
<servlet-class>footmark.servlet.FragmentServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>fragment</servlet-name>
<url-pattern>/fragment</url-pattern>
</servlet-mapping>
</web-fragment>
<web-fragment>
<name>FragmentA</name>
<ordering>
<after>
<name>FragmentB</name>
<name>FragmentC</name>
</after>
<before>
<others/> <!-- 其他所有 fragment -->
</before>
</ordering>
...
</web-fragment>
<name>:標識當前 fragment,供其他 fragment 參考<after>:必須在指定 fragment 之後解析<before>:必須在指定 fragment 之前解析<others/>:除當前外的所有 fragment,優先級低於具名規則運行時動態新增 Servlet/Filter/Listener:
ServletRegistration.Dynamic reg = servletContext.addServlet("dynamic", "com.example.DynamicServlet");
reg.addMapping("/dynamic/*");
reg.setLoadOnStartup(1);
reg.setAsyncSupported(true);
ServletContextListener.contextInitialized() 或 ServletContainerInitializer.onStartup()ServletContainerInitializer:JAR 服務 API 發現,配合 @HandlesTypes 篩選處理類別無需第三方庫 (commons-fileupload 等):
@WebServlet(urlPatterns="/upload", asyncSupported=true)
@MultipartConfig(location="/tmp", maxFileSize=1024*1024*10) // 10MB
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
Part photo = req.getPart("photo"); // 取得單一檔案
photo.write("photo.jpg"); // 一行儲存
// 或 req.getParts() 取得所有檔案
}
}
注意:請求 Content-Type 必須為 multipart/form-data,否則拋異常。
| 特性 | 遷移優先級 | 建議 |
|---|---|---|
| 非同步處理 | 高 | 長輪詢、Comet、WebSocket 前身、耗時業務必用 |
| 註解配置 | 中 | 新專案全註解,舊專案漸進式遷移 |
| 可插拔架構 | 中 | 框架開發者必備,應用層視模組化需求 |
| 檔案上傳 | 高 | 立即替換 commons-fileupload,簡化依賴 |
Servlet 3.0 為現代 Java Web 框架 (Spring MVC 3.2+、Struts 2.3+、JSF 2.2+) 奠定基礎,掌握這些特性能深入理解框架底層運作並寫出高效能應用。