創建一個ServletContextServer類,用來初始化web應用程序的Context,並且指定Servlet和Servlet匹配的url。這里指定了兩個Servlet,分別是HelloServlet和GoodbyeServlet,並分別對應/hello/*和/goodbye/*。
public class ServletContextServer { public static void main(String[] args) throws Exception { Server server = new Server(8080); ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); context.setContextPath("/"); server.setHandler(context); // http://localhost:8080/hello context.addServlet(new ServletHolder(new HelloServlet()), "/hello"); // http://localhost:8080/hello/kongxx context.addServlet(new ServletHolder(new HelloServlet("Hello Kongxx!")), "/hello/kongxx"); // http://localhost:8080/goodbye context.addServlet(new ServletHolder(new GoodbyeServlet()), "/goodbye"); // http://localhost:8080/goodbye/kongxx context.addServlet(new ServletHolder(new GoodbyeServlet("Goodbye kongxx!")), "/goodbye/kongxx"); server.start(); server.join(); } }
也可以創建兩個Context,分別綁定到"/hello"和"/goodbye"
public class MultiContextServer { public static void main(String[] args) throws Exception { Server server = new Server(8080); // http://localhost:8080/hello/kongxx ServletContextHandler context1 = new ServletContextHandler(ServletContextHandler.SESSIONS); context1.setContextPath("/hello"); context1.setResourceBase("."); context1.setClassLoader(Thread.currentThread().getContextClassLoader()); context1.addServlet(new ServletHolder(new HelloServlet("Hello Kongxx!")), "/kongxx"); // http://localhost:8080/goodbye/kongxx ServletContextHandler context2 = new ServletContextHandler(ServletContextHandler.SESSIONS); context2.setContextPath("/goodbye"); context2.setResourceBase("."); context2.setClassLoader(Thread.currentThread().getContextClassLoader()); context2.addServlet(new ServletHolder(new GoodbyeServlet("Goodbye kongxx!")), "/kongxx");
ContextHandlerCollection contexts = new ContextHandlerCollection(); contexts.setHandlers(new Handler[] { context1, context2 }); server.setHandler(contexts); server.start(); server.join(); } }
Jetty 的基本架構
Jetty 是一個Servlet 引擎,它的架構比較簡單,也是一個可擴展性和非常靈活的應用服務器,它有一個基本數據模型,這個數據模型就是 Handler,所有可以被擴展的組件都可以作為一個 Handler,添加到 Server 中,Jetty 就是幫你管理這些 Handler。
整個 Jetty 的核心組件由 Server 和 Connector 兩個組件構成,整個 Server 組件是基於 Handler 容器工作的,Jetty 中另外一個比不可少的組件是 Connector,它負責接受客戶端的連接請求,並將請求分配給一個處理隊列去執行。Jetty 中還有一些可有可無的組件,我們可以在它上做擴展。如 JMX,我們可以定義一些 Mbean 把它加到 Server 中,當 Server 啟動的時候,這些 Bean 就會一起工作。
整個 Jetty 的核心是圍繞着 Server 類來構建,Server 類繼承了 Handler,關聯了 Connector 和 Container。Container 是管理 Mbean 的容器。Jetty 的 Server 的擴展主要是實現一個個 Handler 並將 Handler 加到 Server 中,Server 中提供了調用這些 Handler 的訪問規則。整個 Jetty 的所有組件的生命周期管理是基於觀察者模板設計,實現LifeCycle。
Handler 的體系結構
Jetty 主要是基於 Handler 來設計的,Handler 的體系結構影響着整個 Jetty 的方方面面。下面總結了一下 Handler 的種類及作用:
Jetty 主要提供了兩種 Handler 類型,一種是 HandlerWrapper,它可以將一個 Handler 委托給另外一個類去執行,如我們要將一個 Handler 加到 Jetty 中,那么就必須將這個 Handler 委托給 Server 去調用。配合 ScopeHandler 類我們可以攔截 Handler 的執行,在調用 Handler 之前或之后,可以做一些另外的事情,類似於 Tomcat 中的 Valve;另外一個 Handler 類型是 HandlerCollection,這個 Handler 類可以將多個 Handler 組裝在一起,構成一個 Handler 鏈,方便我們做擴展。
Jetty 的啟動過程
Jetty 的入口是 Server 類,Server 類啟動完成了,就代表 Jetty 能為你提供服務了。它到底能提供哪些服務,就要看 Server 類啟動時都調用了其它組件的 start 方法。從 Jetty 的配置文件我們可以發現,配置 Jetty 的過程就是將那些類配置到 Server 的過程。下面是 Jetty 的啟動時序圖:
因為 Jetty 中所有的組件都會繼承 LifeCycle,所以 Server 的 start 方法調用就會調用所有已經注冊到 Server 的組件,Server 啟動其它組件的順序是:首先啟動設置到 Server 的 Handler,通常這個 Handler 會有很多子 Handler,這些 Handler 將組成一個 Handler 鏈。Server 會依次啟動這個鏈上的所有 Handler。接着會啟動注冊在 Server 上 JMX 的 Mbean,讓 Mbean 也一起工作起來,最后會啟動 Connector,打開端口,接受客戶端請求,啟動邏輯非常簡單。
請求接受過程
Jetty 作為一個獨立的 Servlet 引擎可以獨立提供 Web 服務,但是它也可以與其他 Web 應用服務器集成,所以它可以提供基於兩種協議工作,一個是 HTTP,一個是 AJP 協議。如果將 Jetty 集成到 Jboss 或者 Apache,那么就可以讓 Jetty 基於 AJP 模式工作。下面分別介紹 Jetty 如何基於這兩種協議工作,並且它們如何建立連接和接受請求的。
如果前端沒有其它 web 服務器,那么 Jetty 應該是基於 HTTP 協議工作。也就是當 Jetty 接收到一個請求時,必須要按照 HTTP 協議解析請求和封裝返回的數據。如果 Jboss 的前面在加一個 web 服務器,如 Apache 或者 nginx,這種架構下 servlet 引擎就不需要解析和封裝返回的 HTTP 協議,因為 HTTP 協議的解析工作已經在 Apache 或 Nginx 服務器上完成了,Jboss 只要基於更加簡單的 AJP 協議工作就行了。
我們設置 Jetty 的 Connector 實現類為 org.eclipse.jetty.server.bi.SocketConnector 讓 Jetty 以 BIO 的方式工作(org.mortbay.jetty.nio.SelectChannelConnector),Jetty 在啟動時將會創建 BIO 的工作環境,它會創建 HttpConnection 類用來解析和封裝 HTTP1.1 的協議,ConnectorEndPoint 類是以 BIO 的處理方式處理連接請求,ServerSocket 是建立 socket 連接接受和傳送數據,Executor 是處理連接的線程池,它負責處理每一個請求隊列中任務。acceptorThread 是監聽連接請求,一有 socket 連接,它將進入下面的處理流程。當 socket 被真正執行時,HttpConnection 將被調用,這里定義了如何將請求傳遞到 servlet 容器里,又如何將請求最終路由到目的 servlet(類似servlet的request)。
Jetty 創建接受連接環境需要三個步驟:
- 創建一個隊列線程池,用於處理每個建立連接產生的任務,這個線程池可以由用戶來指定。
- 創建 ServerSocket,用於准備接受客戶端的 socket 請求,以及客戶端用來包裝這個 socket 的一些輔助類。
- 創建一個或多個監聽線程,用來監聽訪問端口是否有連接進來。
當建立連接的環境已經准備好了,就可以接受 HTTP 請求了,Accetptor 線程將會為這個請求創建 ConnectorEndPoint。HttpConnection 用來表示這個連接是一個 HTTP 協議的連接,它會創建 HttpParse 類解析 HTTP 協議,並且會創建符合 HTTP 協議的 Request 和 Response 對象。接下去就是將這個線程交給隊列線程池去執行了。
實際上在 AJP 處理請求相比較 HTTP 時唯一的不同就是在讀取到 socket 數據包時,如何來轉換這個數據包,是按照 HTTP 協議的包格式來解析就是 HttpParser,按照 AJP 協議來解析就是 Ajp13Parserer。封裝返回的數據也是如此。讓 Jetty 工作在 AJP 協議下,需要配置 connector 的實現類為 Ajp13SocketConnector,這個類繼承了 SocketConnector 類,覆蓋了父類的 newConnection 方法,為的是創建 Ajp13Connection 對象而不是 HttpConnection。
請求處理過程
實際上 Jetty 的工作方式非常簡單,當 Jetty 接受到一個請求時,Jetty 就把這個請求交給在 Server 中注冊的代理 Handler 去執行,如何執行你注冊的 Handler同樣由你去規定,Jetty 要做的就是調用你注冊的第一個 Handler 的 handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 方法,接下去要怎么做,完全由你決定。
訪問一個 Servlet 的代碼:
Server server = new Server(); Connector connector = new SelectChannelConnector(); connector.setPort(8080); server.setConnectors(new Connector[]{ connector }); ServletContextHandler root = new ServletContextHandler(null,"/",ServletContextHandler.SESSIONS); server.setHandler(root); root.addServlet(new ServletHolder(new org.eclipse.jetty.embedded.HelloServlet("Hello")),"/"); server.start(); server.join();
創建一個 ServletContextHandler 並給這個 Handler 添加一個 Servlet,這里的 ServletHolder 是 Servlet 的一個裝飾類,它十分類似於 Tomcat 中的 StandardWrapper。
上圖可以看出 Jetty 處理請求的過程就是 Handler 鏈上 handle 方法的執行過程,在這里需要解釋的一點是 ScopeHandler 的處理規則,ServletContextHandler、SessionHandler 和 ServletHandler 都繼承了 ScopeHandler,那么這三個類組成一個 Handler 鏈,它們的執行規則是:ServletContextHandler.handle->ServletContextHandler.doScope ->SessionHandler. doScope->ServletHandler. doScope->ServletContextHandler. doHandle->SessionHandler. doHandle->ServletHandler. doHandle,它這種機制使得我們可以在 doScope 做一些額外工作。
與 Tomcat 的比較
架構比較:
Jetty 的架構從前面的分析可知,它的所有組件都是基於 Handler 來實現,當然它也支持 JMX。但是主要的功能擴展都可以用 Handler 來實現。可以說 Jetty 是面向 Handler 的架構,就像 Spring 是面向 Bean 的架構,iBATIS 是面向 statement 一樣,而 Tomcat 是以多級容器構建起來的,它們的架構設計必然都有一個“元神”,所有以這個“元神“構建的其它組件都是肉身。
Jetty從設計模板角度來看 Handler 的設計實際上就是一個責任鏈模式,接口類 HandlerCollection 可以幫助開發者構建一個鏈,而另一個接口類 ScopeHandler 可以幫助你控制這個鏈的訪問順序。另外一個用到的設計模板就是觀察者模式,用這個設計模式控制了整個 Jetty 的生命周期,只要繼承了 LifeCycle 接口,你的對象就可以交給 Jetty 來統一管理了。所以擴展 Jetty 非常簡單,也很容易讓人理解,整體架構上的簡單也帶來了無比的好處,Jetty 可以很容易被擴展和裁剪。
Tomcat 要臃腫很多,Tomcat 的整體設計上很復雜,前面說了 Tomcat 的核心是它的容器的設計,從 Server 到 Service 再到 engine 等 container 容器。作為一個應用服務器這樣設計無口厚非,容器的分層設計也是為了更好的擴展,這是這種擴展的方式是將應用服務器的內部結構暴露給外部使用者,使得如果想擴展 Tomcat,開發人員必須要首先了解 Tomcat 的整體設計結構,然后才能知道如何按照它的規范來做擴展。這樣無形就增加了對 Tomcat 的學習成本。不僅僅是容器,實際上 Tomcat 也有基於責任鏈的設計方式,像串聯 Pipeline 的 Vavle 設計也是與 Jetty 的 Handler 類似的方式。要自己實現一個 Vavle 與寫一個 Handler 的難度不相上下。表面上看,Tomcat 的功能要比 Jetty 強大,因為 Tomcat 已經幫你做了很多工作了,而 Jetty 只告訴,你能怎么做,如何做,有你去實現。
打個比方,就像小孩子學數學,Tomcat 告訴你 1+1=2,1+2=3,2+2=4 這個結果,然后你可以根據這個方式得出 1+1+2=4,你要計算其它數必須根據它給你的公式才能計算,而 Jetty 是告訴你加減乘除的算法規則,然后你就可以根據這個規則自己做運算了。所以你一旦掌握了 Jetty,Jetty 將變得異常強大。
性能比較:
單純比較 Tomcat 與 Jetty 的性能意義不是很大,只能說在某種使用場景下,它表現的各有差異。因為它們面向的使用場景不盡相同。從架構上來看 Tomcat 在處理少數非常繁忙的連接上更有優勢,也就是說連接的生命周期如果短的話,Tomcat 的總體性能更高。而 Jetty 剛好相反,Jetty 可以同時處理大量連接而且可以長時間保持這些連接。例如像一些 web 聊天應用非常適合用 Jetty 做服務器,像淘寶的 web 旺旺就是用 Jetty 作為 Servlet 引擎。
另外由於 Jetty 的架構非常簡單,作為服務器它可以按需加載組件,這樣不需要的組件可以去掉,這樣無形可以減少服務器本身的內存開銷,處理一次請求也是可以減少產生的臨時對象,這樣性能也會提高。另外 Jetty 默認使用的是 NIO 技術在處理 I/O 請求上更占優勢,Tomcat 默認使用的是 BIO,在處理靜態資源時,Tomcat 的性能不如 Jetty。
特性比較:
作為一個標准的 Servlet 引擎,它們都支持標准的 Servlet 規范,還有 Java EE 的規范也都支持,由於 Tomcat 的使用的更加廣泛,它對這些支持的更加全面一些,有很多特性 Tomcat 都直接集成進來了。但是 Jetty 的應變更加快速,這一方面是因為 Jetty 的開發社區更加活躍,另一方面也是因為 Jetty 的修改更加簡單,它只要把相應的組件替換就好了,而 Tomcat 的整體結構上要復雜很多,修改功能比較緩慢。所以 Tomcat 對最新的 Servlet 規范的支持總是要比人們預期的要晚。