Jetty 是 Eclipse 基金會的開源項目,與 Tomcat 一樣是「HTTP 伺服器 + Servlet 容器」。Jetty 以輕量級和可嵌入性著稱,被廣泛應用於 Google App Engine、Spring Boot 等項目中。通過對比學習 Jetty 和 Tomcat,我們能更深入理解 Web 容器的設計思想。

整體架構#

核心組件#

Jetty 架構三要素

Jetty Server = 多個 Connector + 多個 Handler + 一個線程池

┌────────────────────────────────────────────────────────────┐
│                         Server                              │
│                                                             │
│   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐       │
│   │  Connector  │  │  Connector  │  │  Connector  │       │
│   │   (HTTP)    │  │  (HTTPS)    │  │   (HTTP/2)  │       │
│   └──────┬──────┘  └──────┬──────┘  └──────┬──────┘       │
│          │                │                │               │
│          └────────────────┼────────────────┘               │
│                           │                                 │
│                           ▼                                 │
│                  ┌─────────────────┐                       │
│                  │     Handler     │                       │
│                  │   (Handler Tree)│                       │
│                  └─────────────────┘                       │
│                           │                                 │
│                           ▼                                 │
│                  ┌─────────────────┐                       │
│                  │   ThreadPool    │                       │
│                  │  (全局共享)     │                       │
│                  └─────────────────┘                       │
└────────────────────────────────────────────────────────────┘

與 Tomcat 的架構對比#

特性TomcatJetty
容器結構多層容器 (Engine/Host/Context/Wrapper)Handler 樹狀結構
線程池每個 Connector 獨立線程池全局共享線程池
Service 概念有,包裝 Connector 和 Container無,Connector 直接關聯 Handler
設計風格功能全面,組態豐富輕量級,高度可定制
I/O 模型支持 NIO、NIO.2、APR只支持 NIO

Connector 組件#

Connector 的職責#

Jetty 的 Connector 負責:

  1. 監聽網路連線埠
  2. 接收連線請求
  3. 讀寫網路資料
  4. 封裝應用層協定

核心子組件#

┌───────────────────── Connector ─────────────────────┐
│                                                      │
│   ┌────────────┐                                    │
│   │  Acceptor  │ ← 接收新連線(阻塞方式)           │
│   └─────┬──────┘                                    │
│         │                                            │
│         ▼                                            │
│   ┌─────────────────────┐                           │
│   │   SelectorManager   │ ← 管理多個 Selector       │
│   │  ┌───────────────┐  │                           │
│   │  │ManagedSelector │  │ ← I/O 事件檢測          │
│   │  └───────┬───────┘  │                           │
│   └──────────┼──────────┘                           │
│              │                                       │
│              ▼                                       │
│   ┌──────────────────┐                              │
│   │    EndPoint      │ ← 封裝 Channel               │
│   │   + Connection   │ ← 應用層協定處理             │
│   └──────────────────┘                              │
│                                                      │
└──────────────────────────────────────────────────────┘

Acceptor#

Acceptor 負責接收新連線,使用阻塞方式

public void accept(int acceptorID) throws IOException {
    ServerSocketChannel serverChannel = _acceptChannel;
    if (serverChannel != null && serverChannel.isOpen()) {
        // 阻塞等待新連線
        SocketChannel channel = serverChannel.accept();
        // 設置為非阻塞模式
        channel.configureBlocking(false);
        // 交給 SelectorManager 處理
        _manager.accept(channel);
    }
}

SelectorManager 與 ManagedSelector#

SelectorManager 管理多個 ManagedSelector,ManagedSelector 封裝了 Java NIO 的 Selector:

public class SelectorManager {
    // 管理多個 ManagedSelector
    private final ManagedSelector[] _selectors;

    public void accept(SelectableChannel channel, Object attachment) {
        // 選擇一個 Selector 來處理
        final ManagedSelector selector = chooseSelector();
        // 提交 Accept 任務
        selector.submit(selector.newAccept(channel, attachment));
    }
}

ManagedSelector 的工作流程:

  1. 將 Channel 註冊到 Selector 上
  2. 創建 EndPoint 和 Connection
  3. 將它們與 SelectionKey 綁定
  4. 持續檢測 I/O 事件

EndPoint 與 Connection#

EndPoint 與 Connection 的關係

  • EndPoint:封裝 SocketChannel,負責底層資料讀寫
  • Connection:負責應用層協定解析(類似 Tomcat 的 Processor)

一個 EndPoint 對應一個 Connection。

// 創建 EndPoint 和 Connection
private void createEndPoint(SelectableChannel channel,
                            SelectionKey selectionKey) {
    // 1. 創建 EndPoint
    EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey);

    // 2. 創建 Connection(處理 HTTP 協定)
    Connection connection = _selectorManager.newConnection(channel, endPoint, ...);

    // 3. 綁定
    endPoint.setConnection(connection);
    selectionKey.attach(endPoint);
}

非同步 I/O 模擬#

回呼函式模擬非同步

Jetty 在應用層面通過回呼函式模擬了非同步 I/O:Connection 向 EndPoint 註冊回呼方法,當資料就緒時由 EndPoint 呼叫回呼方法。

// HttpConnection 註冊讀取回呼
getEndPoint().fillInterested(_readCallback);

// 回呼方法中讀取資料
private class ReadCallback implements Callback {
    @Override
    public void succeeded() {
        // 資料就緒,讀取並解析
        onFillable();
    }
}

Connector 工作流程#

1. Acceptor 接收新連線,獲得 SocketChannel

2. Acceptor 將 Channel 交給 SelectorManager

3. SelectorManager 選擇一個 ManagedSelector

4. ManagedSelector 將 Channel 註冊到 Selector

5. ManagedSelector 創建 EndPoint 和 Connection

6. ManagedSelector 檢測到 I/O 事件

7. 呼叫 EndPoint 的方法,回傳一個 Runnable

8. 將 Runnable 提交到線程池執行

9. Runnable 執行時呼叫 Connection 的回呼

10. Connection 讀取資料,解析協定,呼叫 Handler

Handler 組件#

Handler 是什麼?#

Handler 是 Jetty 的靈魂

Jetty 將所有請求處理邏輯都抽象為 Handler。通過組合不同的 Handler,可以實現高度定制化。

public interface Handler extends LifeCycle, Destroyable {
    // 處理請求
    void handle(String target,
                Request baseRequest,
                HttpServletRequest request,
                HttpServletResponse response)
        throws IOException, ServletException;

    // 關聯 Server
    void setServer(Server server);
    Server getServer();
}

Handler 的繼承體系#

Handler (介面)
    └── AbstractHandler
            ├── AbstractHandlerContainer
            │       │
            │       ├── HandlerWrapper (包含單個 Handler)
            │       │       │
            │       │       ├── Server
            │       │       ├── ScopedHandler
            │       │       │       ├── ContextHandler
            │       │       │       ├── SessionHandler
            │       │       │       ├── SecurityHandler
            │       │       │       └── ServletHandler
            │       │       └── WebAppContext
            │       │
            │       └── HandlerCollection (包含 Handler 陣列)
            │               │
            │               └── ContextHandlerCollection
            └── ResourceHandler (靜態資源)

Handler 的三種類型#

類型說明示例
協調 Handler將請求路由到一組 HandlerHandlerCollection
過濾器 Handler處理請求後轉發給下一個 HandlerHandlerWrapper, ContextHandler
內容 Handler真正處理請求,生成回應ServletHandler, ResourceHandler

HandlerWrapper 與 HandlerCollection#

// HandlerWrapper:持有單個 Handler 引用
public class HandlerWrapper extends AbstractHandlerContainer {
    protected Handler _handler;

    @Override
    public void handle(String target, Request baseRequest,
                       HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        if (_handler != null) {
            _handler.handle(target, baseRequest, request, response);
        }
    }
}

// HandlerCollection:持有 Handler 陣列
public class HandlerCollection extends AbstractHandlerContainer {
    private Handler[] _handlers;

    @Override
    public void handle(String target, Request baseRequest,
                       HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        for (Handler handler : _handlers) {
            handler.handle(target, baseRequest, request, response);
        }
    }
}

如何實現 Servlet 規範#

Jetty 通過以下 Handler 實現 Servlet 規範:

Handler對應 Servlet 規範
ContextHandlerServletContext
SessionHandlerHttpSession
SecurityHandler安全約束
ServletHandlerServlet, Filter, Listener
WebAppContext (繼承 ContextHandler)
    └── SessionHandler
            └── SecurityHandler
                    └── ServletHandler ──> Filter Chain ──> Servlet

Handler 鏈的執行流程#

// 啟動一個 Web 應用
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/myapp");
webapp.setWar("myapp.war");

// 將 Handler 添加到 Server
server.setHandler(webapp);

// 啟動
server.start();

請求處理流程:

請求 /myapp/hello
WebAppContext.handle()
    ├── 設置 ClassLoader
    ├── 初始化 ServletContext
SessionHandler.handle()
    ├── 創建或獲取 Session
SecurityHandler.handle()
    ├── 安全檢查
ServletHandler.handle()
    ├── 創建 Filter Chain
    ├── 呼叫 Filters
    └── 呼叫 Servlet

ScopedHandler#

什麼是 ScopedHandler?#

ScopedHandler 實現了「具有上下文資訊的責任鏈呼叫」,確保 doScope() 方法在 doHandle() 之前執行。

呼叫順序:
A.doScope() → B.doScope() → C.doScope()
    → A.doHandle() → B.doHandle() → C.doHandle()

為什麼需要這樣的設計?#

Servlet 規範要求 Servlet 執行時有上下文環境(如 ServletContext、Session)。這些上下文需要在處理請求之前初始化。

  • doScope():初始化上下文(如設置類載入器、創建 Session)
  • doHandle():處理請求
ScopedHandler 的實現原理
public abstract class ScopedHandler extends HandlerWrapper {
    protected ScopedHandler _outerScope;
    protected ScopedHandler _nextScope;

    @Override
    public final void handle(String target, Request baseRequest,
                             HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        if (_outerScope == null) {
            // 頭節點,先執行 doScope
            doScope(target, baseRequest, request, response);
        } else {
            // 非頭節點,執行 doHandle
            doHandle(target, baseRequest, request, response);
        }
    }

    public void doScope(String target, Request baseRequest,
                        HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        nextScope(target, baseRequest, request, response);
    }

    public final void nextScope(String target, Request baseRequest,
                                HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        if (_nextScope != null) {
            // 還有下一個 ScopedHandler,繼續 doScope
            _nextScope.doScope(target, baseRequest, request, response);
        } else if (_outerScope != null) {
            // doScope 都執行完了,回到頭節點開始 doHandle
            _outerScope.doHandle(target, baseRequest, request, response);
        } else {
            doHandle(target, baseRequest, request, response);
        }
    }

    public abstract void doHandle(String target, Request baseRequest,
                                  HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException;
}

嵌入式使用#

Jetty 的嵌入式優勢#

Jetty 是嵌入式 Web 容器的首選

Jetty 設計之初就考慮了嵌入式場景:

  • 輕量級,啟動快
  • API 簡單,易於編程
  • 高度可定制

基本使用示例#

public class EmbeddedJetty {
    public static void main(String[] args) throws Exception {
        // 創建 Server,指定連線埠
        Server server = new Server(8080);

        // 創建 Servlet Handler
        ServletContextHandler context = new ServletContextHandler();
        context.setContextPath("/");
        context.addServlet(new ServletHolder(new HelloServlet()), "/hello");

        // 設置 Handler
        server.setHandler(context);

        // 啟動
        server.start();
        server.join();
    }
}

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        resp.setContentType("text/plain");
        resp.getWriter().println("Hello, Jetty!");
    }
}

完整的 Web 應用組態#

public class FullWebApp {
    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);

        // 組態 Web 應用
        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/app");
        webapp.setResourceBase("src/main/webapp");
        webapp.setDescriptor("src/main/webapp/WEB-INF/web.xml");

        // 添加 Filter
        webapp.addFilter(MyFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));

        // 添加 Listener
        webapp.addEventListener(new MyServletContextListener());

        // 組態 Session
        SessionHandler sessionHandler = new SessionHandler();
        sessionHandler.setMaxInactiveInterval(1800); // 30 分鐘
        webapp.setSessionHandler(sessionHandler);

        server.setHandler(webapp);
        server.start();
        server.join();
    }
}

Spring Boot 中使用 Jetty#

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

Jetty vs Tomcat 深度對比#

架構設計對比#

方面TomcatJetty
容器模型多層容器,固定層級Handler 樹,靈活組合
請求處理Pipeline-Valve 責任鏈Handler 鏈
線程模型每個 Connector 獨立線程池全局共享線程池
I/O 模型NIO, NIO.2, APR僅 NIO
組態方式主要通過 XMLXML 和編程方式都很方便

效能特點對比#

方面TomcatJetty
啟動速度較慢(功能多)
內存佔用較多較少
大文件處理一般優秀(流式處理)
WebSocket支持支持,且效能優秀
HTTP/2支持支持

適用場景對比#

場景推薦原因
傳統企業應用Tomcat生態成熟,文檔豐富
嵌入式應用Jetty輕量級,啟動快
微服務兩者皆可Spring Boot 默認 Tomcat,可切換
高並行長連線Jetty線程模型更適合
需要 APRTomcatJetty 不支持 APR

設計理念對比#

不同的設計哲學

  • Tomcat:功能完整,開箱即用,遵循「約定優於組態」
  • Jetty:輕量靈活,高度可定制,遵循「微內核 + 插件」

總結#

Jetty 的核心設計思想#

  1. 組件化:所有功能都是 Handler,可自由組合
  2. 輕量級:只保留核心功能,其他都是可選
  3. 嵌入式友好:API 設計簡潔,易於編程使用
  4. 非同步優先:通過回呼模擬非同步 I/O

從 Jetty 學到的設計經驗#

設計原則在 Jetty 中的體現
高內聚低耦合Connector 和 Handler 分離
開閉原則通過 Handler 機制擴展功能
單一職責每種 Handler 只做一件事
組合優於繼承Handler 樹的組合方式