- 12.1 Jetty的基本架構
它有一個基本數據模型Handler,所有可以被擴展的組件都可以作為一個Handler添加到Server中,Jetty將幫你管理這些Handler。
Jetty的基本架構簡介
整個Jetty的核心由Server和Connector兩個組件構成,整個Server組件是基於Handler容器工作的,它類似Tomcat的Container容器。Connector組件負責接受客戶端的連接請求,並將請求分配給一個處理隊列去執行。
整個Jetty的核心圍繞着Server類來構建,Server類繼承了Handler,關聯了Connector和Container,Container是管理Mbean的容器。Jetty的Server的擴展主要是實現一個個Handler並將Handler加到Server中,Server中提供了調用這些Handler的訪問規則。
每個組件都會持有一個觀察者(在這里是Listener類)集合,當start、fail或stop等事件被觸發時,這些Listener將會調用。
Handler的體系結構
Handler體系結構影響着整個Jetty的方方面面。主要有兩種Handler類型。一種是HandlerWrapper,它可以將一個Handler委托給另一個類去執行,如我們將一個Handler加到Jetty中,那么必須將這個Handler委托給Server去調用。配合ScopeHandler類可以攔截Handler的執行,在調用Handler之前或之后可以做另外一件事,類似於Tomcat的Valve。另一種Handler類型是HandlerCollection,這個Handler類可以將多個Handler組裝在一起,構成一個Handler鏈。
- 12.2 Jetty的啟動過程
Jetty的入口是Server類,Server類啟動完了,就代表Jetty能提供服務了。在Jetty的配置文件我們可以發現,配置Jetty的過程就是將那些類配置到Server的過程。
因為Jetty的所有組件都會繼承LifeCycle,所以Server的start方法就會調用所有已經注冊到Server的組件,Server啟動其他組件的順序:首先啟動設置Server的Handler,通常這個Handler會有多個子Handler,接着會啟動注冊在Server上JMX的Mbean,讓Mbena也一起工作,最后啟動Connector,打開端口,接受客戶端請求。
- 12.3 接受請求
Jetty作為一個獨立的Servlet引擎可以獨立提供Web服務,但是它也可以與其他Web應用服務器集成,所以它可以基於兩種協議工作,一個是HTTP,另一個是AJP協議。
基於HTTP協議工作
如果前端沒有其他Web服務器,那么Jetty應該基於HTTP協議工作,也就是當Jetty接收到一個請求時,必須按照HTTP協議請求和封裝返回的數據。
我們設置Jetty的Connector實現類為org.eclipse.jetty.server.bi.SocketConnector,讓Jetty以BIO的方式工作。Jetty在啟動BIO的處理方式處理連接請求,ServerSocket用於建立Socket連接以接受和傳送數據,Executor用於處理連接的線程池,它負責處理每一個請求隊列中任務。acceptorThread監聽連接請求,一有Socket連接,它將進入下面處理流程。
當Socket被真正執行時,HttpConnection將被調用.
Jetty創建接受連接環境需要三個步驟:
1)創建一個隊列線程池,用於處理每個建立連接的任務,這個線程池可以由用戶來指定。
2)創建ServerSocket,用於准備接受客戶端的Socket請求,以及客戶端用來包裝這個Socket的一些輔助類。
3)創建一個或多個監聽器,用來監聽訪問端口是否有連接進來
當建立連接的環境已經准備好就可以接受HTTP請求了,當Acceptor接受到Socket連接后轉入下圖流程。
Acceptor線程將會為這個請求創建ConnectorEndPoint,HttpConnection用來表示這個連接是一個HTTP連接,它會創建HttpParse類解析HTTP協議,並且會創建符合HTTP協議的Request和Response對象。接下去將線程交給隊列線程池去執行。
基於AJP工作
通常一個Web服務站點的后端服務器不是將Java的應用服務器直接暴露給服務者,而是在應用服務器的前面再加一個Web服務器,可以做日志分析、負載均衡、權限控制、防止惡意請求以及靜態資源預加載等。
這種架構下Servlet引擎就不需要解析和封裝返回的HTTP協議,因為HTTP協議的解析工作已經在Apache或Naginx服務器上完成,Jboss只要基於更加簡單的AJP協議工作就行了,這樣就能加快請求的響應速度。
實際上AJP處理請求相比於HTTP唯一不同就是Socket數據包時如何來轉換這個數據包,按照HTTP協議的包格式來解析就是HttpParser,按照AJP協議來解析就是Ajp13Parserer。
讓Jetty工作在AJP協議下,需要配置connector的實現類為Ajp13SocketConnector,這個類繼承了SocketConnector類,覆蓋了父類的newConnection方法,為的是創建Ajp13Connection對象而不是HttpConnection。
與HTTP方式唯一不同的地方就是SocketConnector類替換成Ajp13SocketConnector類,改成Ajp13SocketConnector的目的是可以創建Ajp13Connection類,就表示當前這個連接使用的是AJP協議,所以需要用Ajp13Parser類解析AJP協議,處理連接的邏輯都是一樣的。
基於NIO方式工作
Jetty默認connector方式是NIO方式。NIO工作原型:
Selector selector = Selector.open(); ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT); ServerSocketChannel ss = (ServerSocketChannel) key.channel(); SocketChannel sc = ssc.accept(); sc.configureBlocking(false); SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ); Set selectedKeys = selector.selectedKeys();
創建一個Selector相當於一個觀察者打開一個Server端通道,把這個Server通道注冊到觀察者上並且指定監聽事件,。然后遍歷這個觀察者觀察到的事件,取出感興趣的事件再處理。這里有個最核心的是我們不需要為每個被觀察者創建一個線程來監控它隨時發生的事件,而是把這些被觀察者都注冊一個地方統一管理,再由把它觸發的事件統一發給感興趣的程序模塊。這里的核心是能夠統一地管理每個被觀察者的事件,所以我們就可以把服務端的每個建立的連接傳送和接受數據作為一個事件統一管理,這樣就不必每個鏈接需要一個線程來維護了。
這里需要注意的是,很多人認為監聽SelectionKey.OP_ACCEPT事件就已經是非阻塞方式了,其實Jetty仍然用一個線程來監聽客戶端的連接請求,當接收到請求后,把這個請求再注冊到Seletor上,然后才以非阻塞方式執行。還有一個容易誤解的地方,即認為Jetty以NIO方式工作只會有一個線程來處理所有的請求,甚至認為不同用戶會在服務端共享一個線程從而導致基於ThreadLocal的程序會出現問題。其實從Jetty源碼中發現,真正共享一個線程的處理只是在監聽不同連接的數據傳送事件上,如有多個連接已經建立,傳統方式是當沒有數據傳輸時,線程是阻塞的,也就是一直在等待下一個數據的到來,從而NIO的處理方式是只有一個線程在等待所有來凝結的數據的到來,而當某個連接數據到來時Jetty會把它分配給這個連接對應的處理線程去處理,所以不同連接的處理線程仍然是獨立的。
- 12.4 處理請求
實際上Jetty的工作方式非常簡單,當Jetty接收到一個請求時,Jetty就把這個請求交給在Server中注冊的代理Handler去執行,如何執行你注冊的Handler同樣是由自己決定,Jetty要做的就是調用你注冊的第一個Handler的handle(String target,Request baseRequest,HttpServlterRequest request,HttpServletResponse response),方法,接下來完全由你決定怎么做。
要能接受一個Web請求訪問,首先要創建一個ContextHandler,如下代碼所示:
Server server = new Server(8080); ContextHanler context = new ContextHandler(); context.setContextPath("/"); context.setResourceBase("."); context.setClassLoader(Thread.currentThread().getContextClassLoader()); server.setHandler(context); context.setHandler(new HelloHandler()); server.start(); server.join();
當我們在瀏覽器中輸入http:localhost:8080時請求將會代理到Server類的handle方法,Server的handle方法將請求代理給ContextHandler的handle方法,ContextHandler又調用HelloHandler的handle方法。這個調用方式是和Servlet的工作方式類似,在啟動之前初始化,創建對象后調用Servlet的service方法。在Servlet的API中我通常只實現它的一個包裝好的類,在Jetty中也是如此,雖然ContextHandler也只是一個Handler,但是這個Handler通常由Jetty幫你實現。
訪問一個Servlet的代碼:
Server server= new Server(); Connector connector = new SelectChannelConnector(); connector.setPort(8080); server.setConnectors(new Connector[]{ connector}); ServletContextHandler(null,"/",ServletContextHandler.SESSIONS); server.setHandler(root); root.addServlet(root); root.addServlet(new ServletHolder(new org.eclipse.jetty.embeded.HelloServlet("Hello")), "/"); server.start(); server.join();
這段代碼中創建了一個ServletContextHandler並給這個Handler添加一個Servlet,這里的Servletholder是Servlet的一個裝飾類。
從圖中可以看出,Jetty處理請求的過程就是Handler鏈上handle方法的執行過程。這里解釋ScopeHandler的處理規則,ServletContextHandler、SessionHandler和ServletHandler都繼承了ScopeHandler.handle->ServletContextHandler.doHandle->SessionHandler.doHandle->ServletHandler.doHandle
- 12.5 與Jboss集成
Jboss是基於JMX的架構,所以只要符合JM規范的系統或框架都可以作為一個組件加到Jboss,擴展Jboss功能。Jetty作為主要的Servlet引擎當然支持與Jboss集成。
Jetty作為一個獨立的Servlet引擎集成到Jboss需要Jboss的AbstractWebContainer類,這個類實現的是模板模式,其中有一個抽象方法需要子類去實現,它是getDeployer,可以指定創建Web服務的Deployer。Jetty工程中有個jetty-jboss模塊,編譯這個模塊就會產生一個SAR包。
SAR包下面jboss-jetty-6.19目錄下有一個webdefault.xml配置文件,是Jetty默認Web.xml配置,在META-INF目錄下有一個jboss-service.xml文件,這個文件配置了MBean,如下:
<mbean code="org.jboss.jetty.JettyService" name="jboss.web:service=WebServer" xmbean-dd="META-INF/webserver-xmbean.xml"> </mbean>
同樣這個org.jboss.jetty.JettyService類也繼承了org.jboss.web.AbstractWebContainer類,覆蓋了父類的startService方法,這個方法直接調用jetty.start啟動Jetty。
- 12.6 與tomcat的比較
架構比較
設計模板角度,Handler的設計實際上就是一個責任鏈模式,接口類HandlerCollection可以幫助開發者構建一個鏈,而另一個接口類ScopeHandler可以幫助開發者控制這個鏈的訪問順序。另一個設計模板就是觀察者模式,用這個設計模式控制了整個Jetty的生命周期,只要繼承了LifeCycle接口。對象就可以交給Jetty來統一管理。
Tomcat的核心是容器的設計,從server到Service再到engine等container容器,將應用服務器的內部結構暴露給外部使用者,使得如果想擴展就必須得了解整體結構。
性能比較
Tomcat在處理少數非常繁忙的連接上更有優勢
Jetty同時處理大量連接而且可以長時間保持這些連接,使用的是NIO,在處理I/O請求上更占優勢
特性比較
作為一個標准的Servlet引擎,都支持標准的Servlet規范,還有Java EE規范也都支持,Tomcat使用廣泛,支持更全面。Jetty修改簡單,應變快速。