容器潘懊,顧名思義就是用來裝載東西的器具姚糊,在 Tomcat 里,容器就是用來裝載 Servlet 的授舟。
容器的層次結(jié)構(gòu)
Tomcat 設(shè)計(jì)了四種容器救恨,分別是 Engine、Host释树、Context 和 Wrapper 肠槽。這四種容器不是平行關(guān)系擎淤,而是父子關(guān)系。如圖:
Tomcat 通過一種分層的結(jié)構(gòu)秸仙,使得 Servlet 容器具有很好的靈活性
Context表示一個(gè)Web應(yīng)用程序嘴拢;Wrapper表示一個(gè)Servlet,一個(gè)Web應(yīng)用中可能會(huì)有多個(gè)Servlet寂纪;Host代表的是一個(gè)虛擬主機(jī)席吴,或者說一個(gè)站點(diǎn),可以給Tomcat配置多個(gè)虛擬主機(jī)地址捞蛋,而一個(gè)虛擬主機(jī)地址下可以部署多個(gè)Web應(yīng)用程序孝冒;Engine表示引擎,用來管理多個(gè)虛擬站點(diǎn)拟杉,一個(gè)Service最多只能有一個(gè)Engine.
在Tomcat的server.xml配置文件中也可以體現(xiàn)這個(gè)關(guān)系
<Server> //頂層組件庄涡,可以包括多個(gè)Service
<Service> //頂層組件,可包含一個(gè)Engine捣域,多個(gè)連接器
<Connector> //連接器組件啼染,代表通信接口
</Connector>
<Engine> //容器組件,一個(gè)Engine組件處理Service中的所有請(qǐng)求焕梅,包含多個(gè)Host
<Host> //容器組件迹鹅,處理特定的Host下客戶請(qǐng)求,可包含多個(gè)Context
<Context> //容器組件贞言,為特定的Web應(yīng)用處理所有的客戶請(qǐng)求
</Context>
</Host>
</Engine>
</Service>
</Server>
Tomcat使用了組合模式來管理這些容器斜棚。所有容器組件都實(shí)現(xiàn)了Container接口,因此組合模式可以使得用戶對(duì)單容器對(duì)象和組合容器對(duì)象的使用具有一致性该窗。這里的單容器對(duì)象指的是最底層的Wrapper弟蚀,組合容器對(duì)象指的是上面的Context、Host或者Engine酗失。Container(org.apache.catalina.Container)接口定義如下:
public interface Container extends Lifecycle {
public String getName();
public void setName(String name);
public Container getParent();
public void setParent(Container container);
public ClassLoader getParentClassLoader();
public void setParentClassLoader(ClassLoader parent);
public void addChild(Container child);
public void addContainerListener(ContainerListener listener);
public void addPropertyChangeListener(PropertyChangeListener listener);
public Container findChild(String name);
public Container[] findChildren();
public ContainerListener[] findContainerListeners();
public void removeChild(Container child);
public void removeContainerListener(ContainerListener listener);
}
在上面的接口中看到了getParent义钉、SetParent、AddChild和removeChild等方法规肴。用來做對(duì)象的統(tǒng)一管理捶闸。
請(qǐng)求定位Servlet的過程
Mapper組件:將用戶請(qǐng)求的URL定位到一個(gè)Servlet,Mapper組件里保存了Web應(yīng)用的配置信息拖刃,其實(shí)就是容器組件與訪問路徑的映射關(guān)系删壮,比如Host容器里配置的域名、Context容器里的Web應(yīng)用路徑兑牡,以及Wrapper容器里Servlet映射的路徑央碟,可以理解為是一個(gè)多層次的Map。
當(dāng)一個(gè)請(qǐng)求到來時(shí)均函,Mapper組件通過解析請(qǐng)求URl里的域名和路徑亿虽,再到自己保存的Map里去查找菱涤,就能定位到一個(gè)Servlet。一個(gè)請(qǐng)求URL最后只會(huì)定位到一個(gè)Wrapper容器经柴,也就是一個(gè)Servlet狸窘。
匹配過程如圖:
請(qǐng)求 http://user.shopping.com:8080/order/buy 定位過程:
首先根據(jù)協(xié)議和端口號(hào)選定Service和Engine
我們知道Tomcat的每個(gè)連接器都監(jiān)聽不同的端口,比如Tomcat默認(rèn)的HTTP連接器監(jiān)聽8080端口坯认、默認(rèn)的AJP連接器監(jiān)聽8009端口。上面的例子中URL訪問的是8080端口氓涣,因此這個(gè)請(qǐng)求會(huì)被HTTP連接器接收牛哺,而一個(gè)連接器是屬于一個(gè)Service組件的,這樣Service組件就確定了劳吠。一個(gè)Service中除了有多個(gè)連接器引润,還有一個(gè)容器組件,具體來說就是一個(gè)Engine容器痒玩,因此也就意味著Engine也確定了淳附。
然后,根據(jù)域名選定host
Service和Engine確定后蠢古,Mapper組件通過URL中的域名去查找相應(yīng)的Host容器奴曙。
之后,根據(jù)URL路徑找到Context組件
Host確定以后草讶,Mapper根據(jù)URL的路徑來匹配相應(yīng)的Web應(yīng)用的路徑
最后洽糟,根據(jù)URL路徑找到Wrapper(Servlet)
Context確定后,Mapper再根據(jù)web.xml中配置的Servlet映射路徑來找到具體的Wrapper和Servlet.
Pipeline-Valve管道
在Tomcat通過一層一層的父子容器找到某個(gè)Servlet來處理請(qǐng)求的過程中堕战,這些父子容器都會(huì)對(duì)請(qǐng)求做一些處理坤溃。連接器中的Adapter會(huì)調(diào)用容器的Service方法來執(zhí)行Servlet,最先拿到請(qǐng)求的是Engine容器嘱丢,Engine容器對(duì)請(qǐng)求做一些處理后薪介,會(huì)把請(qǐng)求傳給自己的子容器Host繼續(xù)處理,以此類推越驻,最后這個(gè)請(qǐng)求會(huì)傳給Wrapper容器汁政,Wrapper容器會(huì)調(diào)用最終的Servlet來處理。這個(gè)調(diào)用過程就是通過Pipeline-Valve管道來實(shí)現(xiàn)的伐谈。
Pipeline-Valve是責(zé)任鏈模式烂完,責(zé)任鏈模式是指在一個(gè)請(qǐng)求處理的過程中有很多處理者依次對(duì)請(qǐng)求進(jìn)行處理,每個(gè)處理者負(fù)責(zé)做自己相應(yīng)的處理诵棵,處理完成之后將再調(diào)用下一個(gè)處理者繼續(xù)處理抠蚣。
Valve標(biāo)識(shí)一個(gè)處理點(diǎn),比如權(quán)限認(rèn)證和記錄日志履澳。Valve接口中的關(guān)鍵方法:
public interface Valve {
public Valve getNext();
public void setNext(Valve valve);
public void backgroundProcess();
public void invoke(Request request, Response response)
throws IOException, ServletException;
public boolean isAsyncSupported();
}
由于Valve是一個(gè)處理點(diǎn)嘶窄,因此invoke方法就是來處理請(qǐng)求的怀跛,Valve中有g(shù)etNext和setNext方法,有一個(gè)鏈表將Valve連起來柄冲,這個(gè)鏈表就是Pipeline吻谋,如下是Pipeline接口:
public interface Pipeline {
public Valve getBasic();
public void setBasic(Valve valve);
public void addValve(Valve valve);
public Valve[] getValves();
public void removeValve(Valve valve);
public Valve getFirst();
public boolean isAsyncSupported();
public Container getContainer();
public void setContainer(Container container);
public void findNonAsyncValves(Set<String> result);
}
通過Pipeline中的addValve方法,Pipeline維護(hù)了Valve鏈表现横,Valve可以插入到Pipeline中漓拾,對(duì)請(qǐng)求做某些處理。那為什么在Pipeline中沒有invoke方法呢戒祠?因?yàn)檎麄€(gè)調(diào)用鏈的觸發(fā)是Valve來完成的骇两,Valve完成自己的處理后,調(diào)用getNext.invoke() 來觸發(fā)下一個(gè)Valve 調(diào)用姜盈。
每個(gè)容器都有一個(gè)Pipeline對(duì)象低千,只要觸發(fā)這個(gè)Pipeline的第一個(gè)Valve,這個(gè)容器里的Pipeline中的Valve就都會(huì)被調(diào)用到馏颂。不同的容器的Pipeline是通過getBasic方法來實(shí)現(xiàn)的示血。這個(gè)BasicValve處于Valve鏈表的末端,它是Pipeline中必不可少的一個(gè)Valve救拉,負(fù)責(zé)調(diào)用下層容器的Pipeline里的第一個(gè)Valve难审。整個(gè)調(diào)用過程如圖:
整個(gè)調(diào)用過程由連接器中的Adapter觸發(fā),它會(huì)調(diào)用Engine的第一個(gè)Valve:
// calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
Wrapper容器的最后一個(gè)Valve會(huì)創(chuàng)建一個(gè)Filter鏈近上,并調(diào)用doFilter()方法剔宪,最終會(huì)調(diào)到Servlet的service方法。
Value和Filter的區(qū)別
- Valve是Tomcat的私有機(jī)制壹无,與Tomcat的基礎(chǔ)架構(gòu)/API是緊耦合的葱绒。Servlet API是公有的標(biāo)準(zhǔn),所有的Web容器包括Jetty都支持Filter機(jī)制斗锭。
- 另一個(gè)重要的區(qū)別是Valve工作的Web容器級(jí)別地淀,攔截所有應(yīng)用的請(qǐng)求;而Servlet Filter工作在應(yīng)用級(jí)別岖是,只能攔截某個(gè)Web應(yīng)用的所有請(qǐng)求帮毁。如果想做整個(gè)Web容器的攔截,必須通過Value來實(shí)現(xiàn).