首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Servlet 生命周期深度剖析:從加載實例化到銷毀的完整流程與線程安全

     編輯:Java 綜合教學

生命周期四階段圖解

┌─────────────────────────────────────────────────────────────┐
│                    Servlet 生命周期                          │
├─────────────────────────────────────────────────────────────┤
│  1. 加載類文件  →  2. 實例化 (new)  →  3. init() 初始化        │
│       │                │                  │                  │
│       ▼                ▼                  ▼                  │
│  類加載器讀取     反射調用無參構造   容器調用,僅一次         │
│  .class 文件      方法創建對象       可獲取 ServletConfig     │
├─────────────────────────────────────────────────────────────┤
│  4. service() 服務階段 (核心業務處理,多線程並發)              │
│       │                                                     │
│       ▼                                                     │
│  每次請求一調用,容器創建 Request/Response 對象傳入         │
│  開發者責任:保證線程安全!                                 │
├─────────────────────────────────────────────────────────────┤
│  5. destroy() 銷毀階段 → 6. 實例被 GC 回收                   │
│       │                                                     │
│       ▼                                                     │
│  容器關閉/應用卸載前調用,釋放資源 (連接池、線程池、文件句柄) │
└─────────────────────────────────────────────────────────────┘

各階段源碼級詳解

階段 1-3:加載、實例化、初始化

// Tomcat 內部簡化邏輯 (StandardWrapper.loadServlet)
public Servlet loadServlet() {
    // 1. 類加載
    Class servletClass = loader.loadClass(servletClassName);
    
    // 2. 實例化
    Servlet servlet = (Servlet) servletClass.getDeclaredConstructor().newInstance();
    
    // 3. 初始化
    ServletConfig config = new StandardWrapperFacade(this); // 封裝初始化參數
    servlet.init(config); // 僅調用一次!
    
    return servlet;
}

關鍵點:

  • Servlet 類必須有 公共無參構造器 (隱式或顯式)
  • init(ServletConfig) 只調用一次,異常則標記不可用
  • 可通過 ServletConfig.getInitParameter("key") 讀取 web.xml 中的 <init-param>
  • load-on-startup > 0 時啟動階段執行,否則首次請求時執行

階段 4:service() - 請求處理核心

// HttpServlet.service() 核心邏輯
protected void service(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException {
    String method = req.getMethod();
    if ("GET".equals(method)) {
        doGet(req, resp);
    } else if ("POST".equals(method)) {
        doPost(req, resp);
    } else if ("PUT".equals(method)) {
        doPut(req, resp);
    } else if ("DELETE".equals(method)) {
        doDelete(req, resp);
    } else if ("HEAD".equals(method)) {
        doHead(req, resp);
    } else if ("OPTIONS".equals(method)) {
        doOptions(req, resp);
    } else if ("TRACE".equals(method)) {
        doTrace(req, resp);
    } else {
        String errMsg = "HTTP method not supported: " + method;
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, errMsg);
    }
}

線程模型:

  • Servlet 實例 單例 (默認)
  • 每個請求分配一個線程調用 service()
  • 共享實例變量 = 線程不安全

線程安全陷阱實例

public class UnsafeServlet extends HttpServlet {
    private int counter = 0; // 實例變量:所有線程共享!
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws IOException {
        counter++; // 非原子操作:讀取-修改-寫入
        resp.getWriter().println("計數: " + counter); // 併發下結果錯誤
    }
}

解決方案對比

方案代碼示例優點缺點
局部變量 (推薦)int count = 0; // 方法內部天然線程安全、無鎖開銷無法跨請求共享狀態
ThreadLocalThreadLocal tl = new ThreadLocal();線程隔離、生命周期可控需手動 remove() 防洩漏
原子類AtomicInteger counter = new AtomicInteger();無鎖、高性能僅適用簡單計數/狀態
同步鎖synchronized(this) { counter++; }通用、簡單併發度低、死鎖風險
無狀態設計 (最佳)Service/DAO 注入,Servlet 只做轉發天然安全、易測試、符合分層需依賴 IoC 容器 (Spring)

階段 5:destroy() - 優雅關閉

public class ResourceServlet extends HttpServlet {
    private DataSource dataSource;
    private ScheduledExecutorService scheduler;
    
    @Override
    public void init() {
        dataSource = createDataSource(); // 初始化連接池
        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(this::cleanupTask, 1, 1, TimeUnit.HOURS);
    }
    
    @Override
    public void destroy() {
        // 1. 停止定時任務
        if (scheduler != null) {
            scheduler.shutdown();
            try { if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) 
                scheduler.shutdownNow(); } 
            catch (InterruptedException e) { scheduler.shutdownNow(); }
        }
        // 2. 關閉連接池
        if (dataSource instanceof AutoCloseable) {
            try { ((AutoCloseable)dataSource).close(); } catch (Exception e) { log(e); }
        }
        // 3. 其他資源清理
        super.destroy();
    }
}

注意:容器調用 destroy() 後,不再分發請求給該實例,但正在執行的 service() 線程會繼續運行至結束。destroy() 中不應等待這些線程 (除非業務強要求),避免關閉超時。

Servlet 3.0+ 註解式配置

@WebServlet(
    urlPatterns = {"/user", "/user/*"}, 
    loadOnStartup = 1,
    initParams = {
        @WebInitParam(name = "pageSize", value = "20"),
        @WebInitParam(name = "cacheEnabled", value = "true")
    },
    asyncSupported = true // 支持異步處理
)
public class ModernServlet extends HttpServlet {
    @Override
    public void init() {
        int pageSize = Integer.parseInt(getServletConfig().getInitParameter("pageSize"));
        // ...
    }
}

無需 web.xml,容器掃描註解自動註冊。asyncSupported=true 允許使用 req.startAsync() 進行異步處理 (長輪詢、推送)。

完整請求處理流程時序

用戶瀏覽器
    │
    ▼ HTTP Request
┌─────────────────────────────────────┐
│ Tomcat Connector (NIO/BIO)          │
│ 解析 HTTP 協議 → 生成 Request/Response │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│ Engine → Host → Context (Web應用)    │
│ 根據 url-pattern 匹配 Servlet        │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│ Filter Chain (doFilter)             │
│ EncodingFilter → SecurityFilter → ...│
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│ Servlet.service(req, resp)          │
│   ├─ HttpServlet.service()          │
│   │   └─ 分發 doGet/doPost...       │
│   │       ├─ 參數解析/校驗          │
│   │       ├─ 業務邏輯調用 (Service) │
│   │       └─ 結果渲染/轉發          │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│ Filter Chain (反向)                 │
│ 响應頭/體最終提交給 Connector       │
└─────────────────────────────────────┘
    │
    ▼ HTTP Response
用戶瀏覽器

最佳實踐棧單

  1. 繼承 HttpServlet,按需重寫 doGet/doPost
  2. 無狀態設計:業務邏輯委託給 Spring 管理的 Service Bean,Servlet 僅做參數綁定、結果視圖解析
  3. 資源管理:init() 初始化,destroy() 釋放,配合 try-with-resources
  4. 異常統一處理:web.xml 配置 <error-page> 或 Spring MVC @ControllerAdvice
  5. 異步處理長耗時:AsyncContext ctx = req.startAsync(); ctx.start(() -> { ... ctx.complete(); })
  6. 安全基線:XSS 防護 (輸出編碼)、CSRF Token、SQL 注入防護 (參數化查詢)、安全標頭 (CSP, HSTS)

理解 Servlet 生命周期與線程模型,是排查併發 Bug、優化性能、設計高可用 Web 應用的基石。現代框架 (Spring MVC, WebFlux) 雖屏蔽了細節,但底層契約始終未變。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved