請求在Tomcat中的執行流程
本來想寫完Servlet在將筆記中的Tomcat整理出來,但是發現不說清除Tomcat,Servlet根本沒法說清楚,所以,這一篇主要梳理請求在Tomcat中到底發生了什麼。
就像我們知道的Tomcat被作為WEB容器使用,當tomcat接收到請求後會發生下列事情:
之前有一篇文章說了大概的Tomcat的執行,但是,那篇文章將Tomcat和Servlet作為一個Web容器來講了,並不是很詳細
Tomcat簡述
1.Tomcat流程
下面我試圖將Tomcat更加詳細的說明出來:
Tomcat中的六個容器:
容器 | 作用 |
---|---|
Server容器 | 一個StandardServer類例項就表示一個Server容器,server是tomcat的頂級構成容器 |
Service容器 | 一個StandardService類例項就表示一個Service容器,Tomcat的次頂級容器,Service是這樣一個集合:它由一個或者多個Connector組成,以及一個Engine,負責處理所有Connector所獲得的客戶請求。 |
Engine容器 | 一個StandardEngine類例項就表示一個Engine容器。Engine下可以配置多個虛擬主機Virtual Host,每個虛擬主機都有一個域名。當Engine獲得一個請求時,它把該請求匹配到某個Host上,然後把該請求交給該Host來處理,Engine有一個預設虛擬主機,當請求無法匹配到任何一個Host上的時候,將交給該預設Host來處理 |
Host容器 | 一個StandardHost類例項就表示一個Host容器,代表一個VirtualHost,虛擬主機,每個虛擬主機和某個網路域名Domain Name相匹配。每個虛擬主機下都可以部署(deploy)一個或者多個WebApp,每個Web App對應於一個Context,有一個Context path。當Host獲得一個請求時,將把該請求匹配到某個Context上,然後把該請求交給該Context來處理。匹配的方法是“最長匹配”,所以一個path==”“的Context將成為該Host的預設Context。所有無法和其它Context的路徑名匹配的請求都將最終和該預設Context匹配 |
Context容器 | 一個StandardContext類例項就表示一個Context容器。一個Context對應於一個Web Application,一個WebApplication由一個或者多個Servlet組成。Context在建立的時候將根據配置檔案CATALINA_HOME/conf/web.xml和WEBAPP_HOME/WEB-INF/web.xml載入Servlet類。當Context獲得請求時,將在自己的對映表(mappingtable)中尋找相匹配的Servlet類。如果找到,則執行該類,獲得請求的迴應,並返回 |
Wrapper容器 | 一個StandardWrapper類例項就表示一個Wrapper容器,Wrapper容器負責管理一個Servlet,包括Servlet的裝載、初始化、資源回收。Wrapper是最底層的容器,其不能在新增子容器了。Wrapper是一個介面,其標準實現類是StandardWrapper |
-Connector
一個Connector將在某個指定埠上偵聽客戶請求,並將獲得的請求交給Engine來處理,從Engine處獲得迴應並返回客戶。
Tomcat有兩個典型的Connector:
- 一個直接偵聽來自browser的http請求
- 一個偵聽來自其它WebServer的請求
Coyote Http/1.1 Connector 在埠8080處偵聽來自客戶browser的http請求。
Coyote JK2 Connector 在埠8009處偵聽來自其它WebServer(Apache)的servlet/jsp代理請求。
下面是流程圖:
這是一個巨集觀的路線
2.Tomcat的啟動與類的具體實現
一個WEB應用對應一個context容器,也就是servlet執行時的servlet容器。
新增一個web應用時將會建立一個StandardContext容器,並且給這個context容器設定必要的引數,url和path分別代表這個應用在tomcat中的訪問路徑和這個應用實際的物理路徑,這兩個引數與tomcat配置中的兩個引數是一致的。
其中一個最重要的一個配置是ContextConfig,這個類會負責整個web應用配置的解析工作。
最後將這個context容器加入到父容器host中。
接下來會呼叫tomcat的start方法啟動tomcat。
Tomcat的啟動邏輯是基於觀察者模式的,所有的容器都會繼承Lifecycle介面,它管理著容器的整個生命週期,所有容器的修改和狀態改變都會由它通知已經註冊的觀察者。
Tomcat啟動的時序如下:
仔細看看基本上就清楚了,這裡只記錄一下細節:
ContextConfig的init方法
當context容器初始狀態設定Init時,新增到context容器的listener將會被呼叫。ContextConfig繼承了LifecycleListener介面,它是在呼叫Tomcat.addWebapp時被加入到StandardContext容器中的。ContextConfig類會負責整個WEB應用的配置檔案的解析工作。
- ContextConfig的init方法將會主要完成一下工作:
- 建立用於解析XML配置檔案的contextDigester物件
- 讀取預設的context.xml檔案,如果存在則解析它
- 讀取預設的Host配置檔案,如果存在則解析它
- 讀取預設的Context自身的配置檔案,如果存在則解析它
- 設定Context的DocBase
startInternal方法
ContextConfig的init方法完成後,Context容器會執行startInternal方法,這個方法包括如下幾個部分:
- 建立讀取資原始檔的物件
- 建立ClassLoader物件
- 設定應用的工作目錄
- 啟動相關的輔助類,如logger,realm,resources等
- 修改啟動狀態,通知感興趣的觀察者
- 子容器的初始化
- 獲取ServletContext並設定必要的引數
- 初始化“load on startuo”的Servlet
Web應用的初始化
web應用的初始化在14步,下面是該初始化的詳細內容
WEB應用的初始化工作是在ContextConfig的configureStart方法中實現的,應用的初始化工作主要是解析web.xml檔案,這個檔案是一個WEB應用的入口。
Tomcat首先會找globalWebXml,這個檔案的搜尋路徑是engine的工作目錄下的org/apache/catalina/startup/NO-DEFAULT_XML或conf/web.xml。
接著會找hostWebXml,這個檔案可能會在System.getProperty(“catalina.base”)/conf/$ {EngineName}/${HostName}/web.xml.default中。
接著尋找應用的配置檔案examples/WEB-INF/web.xml,web.xml檔案中的各個配置項將會被解析成相應的屬性儲存在WebXml物件中。
接下來會講WebXml物件中的屬性設定到context容器中,這裡包括建立servlet物件,filter,listerner等,這些在WebXml的configureContext方法中。
以及下面是解析servlet的程式碼物件:
for (ServletDef servlet : servlets.values()) {
Wrapper wrapper = context.createWrapper();
String jspFile = servlet.getJspFile();
if (jspFile != null) {
wrapper.setJspFile(jspFile);
}
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
wrapper.setName(servlet.getServletName());
Map<String,String> params = servlet.getParameterMap();
for (Entry<String, String> entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setRunAs(servlet.getRunAs());
Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
for (SecurityRoleRef roleRef : roleRefs) {
wrapper.addSecurityReference(roleRef.getName(), roleRef.getLink());
}
wrapper.setServletClass(servlet.getServletClass());
MultipartDef multipartdef = servlet.getMultipartDef();
if (multipartdef != null) {
if (multipartdef.getMaxFileSize() != null &&
multipartdef.getMaxRequestSize()!= null &&
multipartdef.getFileSizeThreshold() != null) {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation(),
Long.parseLong(multipartdef.getMaxFileSize()),
Long.parseLong(multipartdef.getMaxRequestSize()),
Integer.parseInt(
multipartdef.getFileSizeThreshold())));
} else {
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation()));
}
}
if (servlet.getAsyncSupported() != null) {
wrapper.setAsyncSupported(
servlet.getAsyncSupported().booleanValue());
}
context.addChild(wrapper);
}
3.建立Servlet例項
前面完成了servlet的解析工作,並且被包裝成了StandardWrapper新增到Context容器中,但是它仍然不能為我們工作,它還沒有被例項化。
3.1.建立
如果Servlet的load-on-startup配置項大於0,那麼在Context容器啟動時就會被例項化。
(這個是在web.xml中進行了配置)
前面提到的在解析配置檔案時會讀取預設的globalWebXml,在conf下的web.xml檔案中定義了一些預設的配置項,其中定義了兩個Servlet,分別是org.apache.catalina.servlets.DefaultServlet和org.apache.jsper.servlet.JspServelt,它們的load-on-startup分別是1和3,也就是當tomcat啟動時這兩個servlet就會被啟動。
建立Servlet例項的方式是從Wrapper.loadServlet開始的,loadServlet方法要完成的就是獲取servletClass,然後把它交給InstanceManager去建立一個基於servletClass.class的物件。如果這個Servlet配置了jsp-file,那麼這個servletClass就是在conf/web.xml中定義的org.apache.jasper.servlet.JspServlet。
3.2.初始化
初始化Servlet在StandardWrapper的initServlet方法中,這個方法很簡單,就是呼叫Servlet的init()方法,同時把包裝了StandardWrapper物件的StandardWrapperFacade作為ServletConfig傳給Servlet。
如果該Servlet關聯的是一個JSP檔案,那麼前面初始化的就是JspServlet,接下來會模擬一次簡單請求,請求呼叫這個JSP檔案,以便編譯這個JSP檔案為類,並初始化這個類。
這樣Servlet物件的初始化就完成了。
3.3.容器預設Servlet
每個servlet容器都有一個預設的servlet,一般都叫做default。
例如:tomcat中的 DefaultServlet 和 JspServlet (上面的部分)
4.建立HttpServlet
這一篇細細說說請求是怎麼交到Servlet手上的。此時,包括Servlet都已經初始化化完畢,一切已經準備就緒。
HttpServletRequest的產生大概在第四步
步驟 | 過程 | 詳情 |
---|---|---|
1 | AbstractEndpoint類及其子類來處理。 | AbstractEndpoint這個抽象類中有一個抽象內部類Acceptor,這個Acceptor的實現類是AbstractEndpoint的三個子類的內部類Acceptor來實現的。 |
1 | 我們的請求就是被這Acceptor監聽到並且接收的。這個類其實是一個執行緒類,因為AbstractEndpoint.Acceptor實現了Runnable介面。 | |
1 | AprEndpoint接收請求的過程。就是用一個接收器接收請求,過程中會使用套接字。但是好像並不是有的請求都會用這個Acceptor來接收。 | |
1 | 當接收請求完畢,經過一系列的處理後就會由AprEndpoint的內部類SocketProcessor來將請求傳給ProtocolHandler來處理。這個SocketProcessor也是一個執行緒類。它有一行程式碼將套接字傳給了第二步來處理。 | |
2 | 在/AbstractConnectionHandler接收到第一步傳來的套接字以後,對套接字進行處理 | wapper就是套接字包裝類的物件,這裡還是理解為套接字,套接字在這裡傳給了第③步的Processor介面的例項。state = processor.process(wrapper); |
3 | 第二步完成後,就會交給Processor介面的實現類來處理。 | 在這裡將會建立請求和響應,但不是我們熟悉的HttpServletRequest或HttpServletResponse型別或其子型別。而是org.apache.coyote.Request 和 org.apache.coyote.Response型別的 |
3 | 它將會在Connector中來進行處理,我說的是處理而不是型別轉換是因為org.apache.coyote.Request 和HttpServletRequest並不是父子關係的類,總之,HttpServletRequest的請求是由Connector來建立,在CoyoteAdapter中處理成HttpServletRequest. | |
3 | 這個方法就是請求物件、響應物件和套接字進行資訊互動的地方,也就是真真正正將套接字中的資訊轉化為請求資訊,還要把響應資訊寫到套接字中。 | |
4 | 第三步完成之後交給CoyoteAdapter來處理 | CoyotoAdapter是將請求傳入Server容器的切入點。Adapter. This represents the entry point in a coyote-based servlet container. |
4 | (具體怎麼處理成HttpServletRequest看程式碼) | CoyoteAdapter中有一個service()方法。這個方法持有一個Connector的引用。這個Connector又持有一個Service容器的引用,而Service容器有持有一個Container(Container的實現類有StandardEngine、StandardHost等等)的引用。所以CoyoteAdapter就可以根據這些引用將請求傳遞到Server容器中了。 |
5 | 如果上面的請求傳遞到的Container是StandaradEngine,那麼就會Engine就會呼叫它持有的StandardPipeline物件來處理請求。 | StandardPipeline就相當於一條管道,這條管道中的有許多閥門,這些閥門會對請求進行處理,並且控制它下一步往哪裡傳遞。StandardEngine的管道使用的閥門是StandardEngineValve。 |
6 | 容器繼續逐層傳遞 | 和StandardEngine一樣,StandardHost、StandardContext、StandardWrapper這幾個容器都擁有自己的一條管道StandardPipeline來處理的請求。但是需要注意的是他們使用的閥門是不一樣的。StandardHost則會使用StandardHostValve,其他的同理。 |
7 | 當最後一個StandardWrapperVale處理完請求後,此時請求已經到達了最底層的容器了。 | StandardWrapper就是最底層的容器,它不允許再有子容器。其實每一個StandardWrapper代表一個Servlet,因為每一個StandardWrapper都會持有一個Servlet例項的引用。 |
//CoyoteAdapter的service(...)方法
/**
* Service method.
*/
@Override
public void service(org.apache.coyote.Request req,
org.apache.coyote.Response res)
throws Exception {
//下面這兩行程式碼就是將請求處理後轉化為HttpServletRequest的
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
//如果請求為null就讓Connector來建立。
if (request == null) {
// Create objects
request = connector.createRequest();
request.setCoyoteRequest(req);
response = connector.createResponse();
response.setCoyoteResponse(res);
//省略部分程式碼
}
if (connector.getXpoweredBy()) {
response.addHeader("X-Powered-By", POWERED_BY);
}
boolean comet = false;
boolean async = false;
boolean postParseSuccess = false;
try {
// Parse and set Catalina and configuration specific
// request parameters
req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container 在這裡將請求傳到Server容器中。
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
//省略部分程式碼
}
} /**
5.Tomcat核心類
Tomcat的體系結構:
一個Connector+一個Container構成一個Service,Service就是對外提供服務的元件,有了Service元件Tomcat就可以對外提供服務了。
那麼這些個元件到底是幹嘛用的呢?
Connector是一個聯結器,主要負責接收請求並把請求交給Container
Container就是一個容器,主要裝的是具體處理請求的元件。
Service主要是為了關聯Container與Connector,一個單獨的Container或者一個單獨的Connector都不能完整處理一個請求,只有兩個結合在一起才能完成一個請求的處理。
5.1 Tomcat的兩大元件
1. Connecter元件
一個Connecter將在某個指定的埠上偵聽客戶請求,接收瀏覽器的發過來的 tcp 連線請求,建立一個 Request 和 Response 物件分別用於和請求端交換資料,然後會產生一個執行緒來處理這個請求並把產生的 Request 和 Response 物件傳給處理Engine(Container中的一部分),從Engine出獲得響應並返回客戶。
Tomcat中有兩個經典的Connector,一個直接偵聽來自Browser的HTTP請求,另外一個來自其他的WebServer請求。Cotote HTTP/1.1 Connector在埠8080處偵聽來自客戶Browser的HTTP請求,Coyote JK2 Connector在埠8009處偵聽其他Web Server的Servlet/JSP請求。
Connector 最重要的功能就是接收連線請求然後分配執行緒讓 Container 來處理這個請求,所以這必然是多執行緒的,多執行緒的處理是 Connector 設計的核心。
2. Container元件
主容器,包含Tomcat的各個容器。
Container是容器的父介面,由四個容器組成,分別是Engine,Host,Context,Wrapper。其中Engine包含Host,Host包含Context,Content包含Wrapper,一個Servlet class對應一個Wrapper
6.總結
這篇文章是整理多篇文章所得,並不能保證完全正確,如有錯誤,感謝指正。
附件:
server.xml的配置:server.xml
相關推薦
Springboot打包放入tomcat中執行
pom.xml調整 1.1 打包方式修改 <packaging>jar</packaging> 變更為 <packaging>war</packaging> 1.2. 新增依賴 重點:scope是provided <d
修改Springboot專案能在tomcat中執行 (jar to war)
0.準備工作 Springboot專案 (pom.xml <packaging>jar</packaging>) 1.新增Jar包 <dependency>
Spring Boot-8 專案部署到獨立的tomcat中執行
Spring Boot預設提供內嵌的tomcat,所以可以直接生成jar包,用java -jar命令就可以啟動。 也可以打成war包,放到tomcat中執行。步驟如下: 步驟一: 將p
Linux環境下更換Tomcat中執行的專案
首先需要進入Tomcat的bin目錄下去使用./shutdown.sh命令去關閉當前tomcat,但是由於關不完全,所以還需要用到如下的命令 檢視當前執行專案,然後再使用如下命令關掉tomcat -9後面的引數是每次都會產生變化的,所以這個是需要自己手動去觀察輸入
Springboot打成war包並在tomcat中執行的部署方法
把spring-boot專案按照平常的web專案一樣釋出到tomcat容器下 一、修改打包形式 在pom.xml裡設定 <packaging>war</packaging> 二、移除嵌入式tomcat外掛 在pom.xml裡找到spring-b
Dockerfile 之 tomcat中執行MyEclipse搭建Web專案(Docker系列)
本文章來自【知識林】 在之前的講解中主要講述的是如何使用已經存在的Docker映象,當然這些映象對我們的使用肯定有很大的幫助,但很多時候我們是需要執行我們自己所定製開發的應用程式,這些應用程式在Doc
SpringBoot學習-(十六)SpringBoot釋出到獨立的tomcat中執行
儘管Spring Boot專案會內建一個tomcat,僅只需通過一個簡單的指令便可啟動專案,但在生產環境下,我們還是習慣將專案釋出到第三外的servlet容器中,下面將介紹如果將一個Spring Boot專案部署到第三方tomcat中執行。 1)修改工程的
linux下發布web專案到tomcat部署執行流程
1、下載tomcat,並上傳到Linux對應目錄下,解壓tomcat壓縮包。 解壓命令:tar xzvf 壓塑包名稱 2、將你的web專案打成war包上傳到tomcat下的webapps下 3、通過命令進入tomcat下的bin目錄下,執行
Springboot打成war包並在tomcat中執行
把spring-boot專案按照平常的web專案一樣釋出到tomcat容器下 一、修改打包形式 在pom.xml裡設定 <packaging>war</packaging
SpringBoot專案釋出到Tomcat中執行
SpringBoot雖然自帶了Tomcat , 但是因為實際開發中可能會需要把幾個專案放到一個Tomcat中. 所以做了一個測試,把Boot專案釋出到Tomcat執行. 1.修改pom.xml檔案. 打包方式改為war包 並取消自帶的tomcat 2 修改
SpringBoot釋出到獨立的tomcat中執行
在開發階段我們推薦使用內嵌的tomcat進行開發,因為這樣會方便很多,但是到生成環境,我希望在獨立的tomcat容器中執行,因為我們需要對tomcat做額外的優化,這時我們需要將工程打包成war包進行釋出。工程的打包方式為war包1、將spring-boot-starter-
idea匯入maven專案並配置在tomcat中執行 (轉)
1:匯入一個maven專案 一定要選擇maven. 然後一路next,最後完成 2,配置tomcat執行 。部署到tomcat我們可以有兩種方式,一種是利用tomcat外掛來進行部署,另一種是下載tomcat伺服器來來進行配置 這裡只介紹第二種 a.下
Eclipse中匯入Maven Web專案並配置其在Tomcat中執行
首先我通過svn將公司的專案checkout到了本地。 因為Maven遵循的是規約比配置重要的原則,所以Maven專案的結構一般是進入目錄後是一個pom.xml檔案和一個src資料夾,當然可能還存在一些README之類的這些都不重要,最關鍵的就是pom.xml和src資料夾
請求在Tomcat中的執行流程
本來想寫完Servlet在將筆記中的Tomcat整理出來,但是發現不說清除Tomcat,Servlet根本沒法說清楚,所以,這一篇主要梳理請求在Tomcat中到底發生了什麼。 就像我們知道的Tomcat被作為WEB容器使用,當tomcat接收到請求後會發生下列
Nginx配置,請求到tomcat中
頁面 需要 服務器 並發 html 處理 htm shift cati 一.web服務器分為兩類 1.web服務器 1)Apache服務器 2)Nginx 3)IIS 2.web 應用服務器 1)tomcat 2)resin
Spring MVC 執行(請求-->響應)流程
resolve pro 日期 格式化數字 bind sha 有效 數字 入參 *每日一句:*每天起床之前有兩個選擇,要麽繼續趴下做你沒做完的夢,要麽起床完成你沒有完成的夢想。-----** ----- 用戶想服務器發型請求,請求被Spring的前端控制器Dispatch
eclipse中執行tomcat出現錯誤:-Djava.endorsed.dirs=/users/huchao/library/tomcat-9/endorsed is not supported
-Djava.endorsed.dirs=/users/huchao/library/tomcat-9/endorsed is not supported. Endorsed standards and standalone APIs in modular form will be supported vi
Ecplise中tomcat上執行HTML檔案自動載入
首先說什麼熱部署 今天在做一個SpringBoot的專案時候,發現一個很煩的問題,我修改了HTML然後ecplise上面在server上面執行,但是讓人傷心的是修改的HTML檔案都都需要重啟tomcat 真的太讓人難受了,不可能這樣一直重新啟動啊 修改了半天沒有看出來怎麼解決這樣的
C# 程式執行中的流程控制
1、C#之流程控制語句:計算機程式執行的控制流程由三種基本的控制結構控制,即順序結構,選擇結構,迴圈結構。 1) 順序結構:從上到下,按照書寫順序執行每一條語句,不會發生跳躍。 程式碼段1; // 先執行程式碼段1 程式碼段2; // 接著執行程式碼段2 ... 2)選擇結構:對
java web專案在tomcat中以除錯模式執行
轉載地址:https://blog.csdn.net/gk_12/article/details/79689702 自己測試環境是: JDK1.8 Eclipse4.5 tomcat 8.5 如果只是將專案部署在tomcat中並不能進行除錯,所以還需要進行配置一下,步驟如下: