Tomcat的实现原理
Tomcat 是一個Web容器。作為 Web 應用的容器承載著 Web 請求處理和響應的工作
最開始用戶通過瀏覽器查看諸如新聞之類的靜態資源,此時就需要通過 HTTP 服務器向瀏覽器返回靜態 HTML 資源,瀏覽器將解析的 HTML 呈現給使用者。這里的 Web 容器就是用來存放 HTTP 服務器,能夠處理網絡請求并且進行響應。
隨著互聯網的發展,用戶需求從靜態資源轉向了動態資源的獲取,同時瀏覽器在資源獲取的同時還會與服務端進行一些交互。
由此 Web 容器的功能開始有了擴展,除了能夠處理 HTTP 請求,還需要 HTTP 服務器調用服務端程序也就是常說的 Web 應用。
針對這種需求,Sun 公司推出了 Servlet 技術, Servlet 是運行在服務端的 Java 小程序。
由于 Servlet 不能獨立運行,因此需要由一個 Servlet 容器來承載它,并且對其進行初始化啟動等管理操作。
為了承載 Servlet,也加入了 Servlet 容器,不過每個 Servlet 都代表一個業務類,包含了一些業務應用如果都接入到 Web 容器中會用戶提供統一的服務響應就需要遵循統一的接口。
說白了就是要遵守一定規則才能放到 Servlet 容器中,方便進行管理,那么這個規則就是 Servlet 接口。從圖中可以看出 Servlet 接口會對單個的 Servlet 進行標準定義。
對于Tomcat而言,它并不知道我們會有什么樣的方法,這些都只是在項目被部署進webapp下后才確定的,由此分析,必然用到了Java的反射來實現類的動態加載、實例化、獲取方法、調用方法。但是我們部署到Tomcat的中的Web項目必須是按照規定好的Servlet 接口來進行編寫,以便進行調用
public interface Servlet {public void init(ServletConfig config) throws ServletException;public ServletConfig getServletConfig();public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;public String getServletInfo();public void destroy(); }對于 Servlet 接口而言定義了 init 方法用做 Servlet 資源的初始化,同時也定義 destroy 方法用做 Servlet 資源的釋放。
其中 Service 方法用來實現具體的業務需求,可以看到該方法傳入 ServletRequest 和 ServletResponse 兩個參數,分別表示封裝了用戶的請求信息和 Servlet 的響應信息。
接口中的 getServletConfig 方法會返回 ServletConfig,ServletConfig 是用來封裝 Servlet 的初始化參數的,可以在 web.xml 配置 Servlet 參數,然后通過 getServletConfig 方法獲取參數。
上面介紹了 Servlet 接口一下,再通過圖 5 對 Servlet 接口調用的周邊類進行深入了解。
如圖 5 所示,Servlet 接口依賴 ServletConfig 接口,該接口正好是用來處理 Servlet 配置參數的,ServletConfig 接口同時也會關聯 ServletContext 獲取 Servlet 上下文的信息。
Servlet 接口中的 service 方法依賴兩個參數分別是 ServletRequest 和 ServletResponse。
同時有兩個接口 HttpServletRequest 和 HttpServletResponse 會分別繼承 ServletRequest 和 ServletResponse。
一般而言 Servlet 作為接口需要具體的實現類去實現這個接口,因此 Servlet 規范提供了一個抽象類名叫 GenericServlet,它實現了 Servlet。
接著有一個 HttpServlet 的類繼承 GenericServlet,為了處理 HTTP 請求這類也會依賴 HttpServletRequest 和 HttpServletResponse。
Servlet 接口定義是 Servlet 容器的重要組成部分,Servlet 容器通過接口去管理接入的 Servlet 實體。
在了解 Servlet 接口規范和 Servlet 容器以后,我們知道如果需要加載不同的動態資源(Web 應用)需要利用 Servlet 容器去加載對應的 Servlet,那么這個加載過程是如何進行的?
我們接下來看看 Servlet 的請求和響應流程。
如圖 6 所示,這里通過 8 個步驟展示了 HTTP 請求和響應的流程:
1.用戶通過瀏覽器發起 HTTP 請求。
2.Servlet 容器在接受到請求以后會對 HTTP 請求進行解析。
3.通過解析的結果以及配置信息創建 Servlet 實例。
4.實例創建以后調用 Servlet 實例的 init 方法完成實例初始化工作。
5.接下來就是調用 Servlet 中的 Service 方法完成具體業務。
6.Service 方法完成以后會將響應信息返回給 Servlet 容器。
7.Servlet 容器將 Servlet 返回的信息創建成 HTTP 響應返回給瀏覽器端。
8.最后容器關閉的時候,Servlet 容器調用 destroy 方法卸載掉 Servlet,并且釋放對應的資源。
Tomcat的結構比較復雜,但是又比較模塊化,所以只要我們找到了最核心的模塊,對于tomcat的整體架構和工作原理就很好理解了。
Connector(連接器)組件負責生成請求對象和響應對象的,Tomcat默認為HttpConnector,負責根據收到的Http請求報文生成Request對象和Response對象,并把這兩個對象傳遞給Container,然后根據Response中的內容生成相應的HTTP報文。
Container是容器的父接口,所有子容器都必須實現這個接口,簡單來說就是服務器部署的項目是運行在Container中的。Container里面的項目獲取到Connector傳遞過來對應的的Request對象和Response對象進行相應的操作。
Connector可以根據不同的的設計和應用場景進行替換,而一個Container可以對應多個Connector。多個Connector和一個Container就形成了一個Service,而Service可以對外提供服務。
而service由server提供生存環境并控制其生命周期,
假設有一個請求http://localhost:8080/test/index.jsp 我們梳理一下流程
1.請求先發送到本機端口8080,然后被在那里偵聽的Coyote HTTP/1.1 Connector獲得;
2. Connector把該請求交給它所在的Service的Engine來處理,并等待Engine的回應;
3.Engine獲得請求localhost:8080/test/index.jsp,匹配它所有虛擬主機Host;
4.Engine匹配到名為localhost的Host(即使匹配不到也把請求交給該Host處理,因為該Host被定義為該Engine的默認主機);
5.localhost Host獲得請求/test/index.jsp,匹配它所擁有的所有Context;
6.Host匹配到路徑為/test的Context(如果匹配不到就把該請求交給路徑名為"“的Context去處理);
7.path=”/test"的Context獲得請求/index.jsp,在它的mapping table中尋找對應的servlet;
8.Context匹配到URL PATTERN為*.jsp的servlet,對應于JspServlet類;
9.構造HttpServletRequest對象和HttpServletResponse對象,作為參數調用JspServlet的doGet或doPost方法;
10.Context把執行完了之后的HttpServletResponse對象返回給Host;
11.Host把HttpServletResponse對象返回給Engine;
12.Engine把HttpServletResponse對象返回給Connector;
13.Connector把HttpServletResponse對象返回給客戶browser;
上面我們知道了流程,但是connector是如何接受請求?又如何封裝成Request和Response對象的呢?
Connector是使用ProtocolHandler來處理請求的,不同的ProtocolHandler代表不同的連接類型,比如我們之前說到的Http11Protocol使用的是普通Socket來連接的,Http11NioProtocol使用的是NioSocket來連接的
可以看到ProtocolHandler由包含了三個部件:Endpoint、Processor、Adapter
Endpoint用來處理底層Socket的網絡連接,由于是處理底層的Socket網絡連接,因此Endpoint是用來實現TCP/IP協議的,
Processor用于將Endpoint接收到的Socket封裝成Request,用來實現HTTP協議的
Adapter用于將Request交給Container進行具體的處理,用來將請求適配到Servlet容器進行具體的處理
Endpoint的抽象實現類AbstractEndpoint里面定義的Acceptor和AsyncTimeout兩個內部類和一個Handler接口。Acceptor用于監聽請求,AsyncTimeout用于檢查異步Request的超時,Handler用于處理接收到的Socket,在內部調用Processor進行處理。
到了這里,我們可以回答上面的問題了,但是Container是如何進行處理的以及處理完之后是如何將處理完的結果返回給Connector我們還不清楚,
Engine:引擎,用來管理多個站點,一個Service最多只能有一個Engine;
Host:代表一個站點,也可以叫虛擬主機,通過配置Host就可以添加站點;
Context:代表一個應用程序,對應著平時開發的一套程序,或者一個WEB-INF目錄以及下面的web.xml文件;
Wrapper:每一Wrapper封裝著一個Servlet的一個或多個實例;
Context和Host的區別是Context表示一個應用,Tomcat中默認配置下webapps下的每一個文件夾目錄都是一個Context而整個webapps就是一個Host站點
我們訪問應用Context的時候,
如果是Host(webapps)下的其他應用,則可以使用http://www.test.com/docs進行訪問,
默認指定的根應用(ROOT)是可以進行改變的
Container如何處理請求
Container處理請求是使用Pipeline-Valve管道來處理的
Pipeline-Valve是責任鏈模式,責任鏈模式是指在一個請求處理的過程中有很多處理者依次對請求進行處理,每個處理者負責做自己相應的處理,處理完之后將處理后的請求返回,再讓下一個處理著繼續處理。
每個Pipeline都有特定的Valve,而且是在管道的最后一個執行,這個Valve叫做BaseValve,BaseValve是不可刪除的;
在上層容器的管道的BaseValve中會調用下層容器的管道。
當執行到StandardWrapperValve的時候,會在StandardWrapperValve中創建FilterChain,并調用其doFilter方法來處理請求,這個FilterChain包含著我們配置的與請求相匹配的Filter和Servlet,其doFilter方法會依次調用所有的Filter的doFilter方法和Servlet的service方法,這樣請求就得到了處理!
當所有的Pipeline-Valve都執行完之后,并且處理完了具體的請求,這個時候就可以將返回的結果交給Connector了,Connector在通過Socket的方式將結果返回給客戶端。
如果執行過程中間出現問題就拋異常。
總結
以上是生活随笔為你收集整理的Tomcat的实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【数据结构与算法】【算法思想】【算法应用
- 下一篇: 互联网产品跨部门沟通的10个原则(转)