前面的文章中,我們介紹了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容器時,會經過以下流轉步驟:
- Tomcat的連接器監聽SocketServer,發現這個請求,按照指定的協議和IO方式處理請求Socket消息,解析Socket為對應的Request實體,並提供回寫報文的Response實體。
- 連接器將Request/Response交給Engine容器,Engine容器存儲了請求域名和Host容器之間的映射關系。比如"www.krishnan.com"域名對應於krishnan_webapps Host容器。
- Engine容器將請求交給對應的Host容器,Host容器開始解析請求中的路徑,如果配置了路徑和應用之間的關系,比如"/appB"對應於appB Context容器,Host容器會安裝配置將請求交給對應應用的Context容器。
- Host容器解析路徑並將應用交給Context容器之后,如果一個應用有多個Servlet,那么這個應用的Context容器也會包含多個Wrapper容器,我們可以通過路徑來將不同的請求映射到不同的Servlet容器。比如圖中的"/servletB"對應servletB Wrapper容器,Context容器將請求交給Wrapper容器。
- 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
本文最先發布至微信公眾號,版權所有,禁止轉載!