前面的文章中,我們介紹了Tomcat的Engine和Host容器,我們知道一個Tomcat最多隻有一個Engine容器,一個Engine容器可以包含多個Host容器,請求中的不同的Host對應不用的Host容器。本章我們會介紹Tomcat的另外兩個容器:Context容器和Wrapper容器。一個Host容器可以包含多個Context容器:同一個Host下面可以包含多個應用,每個應用對應一個Context容器。一個Context容器又可以包含多個Wrapper容器:每個Wrapper容器包含一個Servlet容器,意味著Tomcat允許一個應用有多個servlet實現。

Tomcat請求流程

我們現在知道Tomcat最重要的元件是聯結器和四種類型的容器,下面的圖展示了Tomcat的一次請求是如何在聯結器和四種容器之間流轉的,假設Http請求頭為"GET /appB/servletB/some-url HTTP/1.1 Host: www.krishnan.com",當請求訪問到Tomcat容器時,會經過以下流轉步驟:

  1. Tomcat的聯結器監聽SocketServer,發現這個請求,按照指定的協議和IO方式處理請求Socket訊息,解析Socket為對應的Request實體,並提供回寫報文的Response實體。
  2. 聯結器將Request/Response交給Engine容器,Engine容器儲存了請求域名和Host容器之間的對映關係。比如"www.krishnan.com"域名對應於krishnan_webapps Host容器。
  3. Engine容器將請求交給對應的Host容器,Host容器開始解析請求中的路徑,如果配置了路徑和應用之間的關係,比如"/appB"對應於appB Context容器,Host容器會安裝配置將請求交給對應應用的Context容器。
  4. Host容器解析路徑並將應用交給Context容器之後,如果一個應用有多個Servlet,那麼這個應用的Context容器也會包含多個Wrapper容器,我們可以通過路徑來將不同的請求對映到不同的Servlet容器。比如圖中的"/servletB"對應servletB Wrapper容器,Context容器將請求交給Wrapper容器。
  5. Context容器按照路徑將請求交給對應的Wrapper容器,Wrapper容器會載入對應的Servlet實現類,呼叫servlet實現類中的邏輯處理Request並將處理結果寫入Response中。

Context容器

我們在配置Tomcat應用程式的時候,總是需要配置一個web.xml檔案,這個檔案就是Context容器去解析的。tomcat預設的web.xml的配置如下所示,該配置中配置了兩個WrapperContext,分別對應於兩個Servlet配置。在web.xml中,我們經常可以看到listener標籤,主要是用於監聽Context容器的宣告週期事件。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<request-character-encoding>UTF-8</request-character-encoding>
<response-character-encoding>UTF-8</response-character-encoding>
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet> <servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet> <servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping> <!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping> <session-config>
<session-timeout>30</session-timeout>
</session-config> <welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list> </web-app>

Wrapper容器

Wrapper容器是最小的容器,每個Wrapper都可以對應一個Servlet的例項。當請求轉發到Wrapper容器之後,wrapper容器在呼叫Pipeline方法之後,會使用特定的類載入器去載入servlet類,對servlet進行例項化和初始化,然後將請求交給servelt的service方法進行處理。

我們常見的Spring的DispatchServlet是執行緒安全的,所以Tomcat不需要保證Servlet的併發安全。對於非執行緒安全的servlet,則可以通過SingleThreadModel來保證多請求下servlet的正常執行。

Wrapper容器的主要作用就是載入servlet類並進行例項化,並呼叫service方法。當第一次請求某個servlet類的時候,Wrapper容器會載入servlet類。Tomcat提供了專門的類載入器用於載入servlet,關於這個類載入器我會在我的其它文章中介紹。

Wrapper容器的基本閥門StandardWrapperValve還會在呼叫servelt容器之前呼叫使用者配置的過濾器鏈Filter。

我是御狐神,歡迎大家關注我的微信公眾號:wzm2zsd

本文最先發布至微信公眾號,版權所有,禁止轉載!