本文轉(zhuǎn)自:http://www.dev-share.com/java/99953page2.html
0:前言
我們知道了tomcat的整體框架了, 也明白了里面都有些什么組件, 以及各個(gè)組件是干什么用的了。
http://www.csdn.net/Develop/read_article.asp?id=27225
我想,接下來(lái)我們應(yīng)該去了解一下 tomcat 是如何處理jsp和servlet請(qǐng)求的。
1. 我們以一個(gè)具體的例子,來(lái)跟蹤TOMCAT,看看它是如何把Request一層一層地遞交給下一個(gè)容器,并最后交給Wrapper來(lái)處理的。
以http://localhost:8080/web/login.jsp為例子
(以下例子,都是以tomcat4 源碼為參考)
這篇心得主要分為3個(gè)部分: 前期, 中期, 和末期。
前期:講解了在瀏覽器里面輸入一個(gè)URL,是怎么被tomcat抓住的。
中期:講解了被tomcat抓住后,又是怎么在各個(gè)容器里面穿梭, 最后到達(dá)最后的處理地點(diǎn)。
末期:講解到達(dá)最后的處理地點(diǎn)后,又是怎么具體處理的。
2、 前期 Request的born.
在這里我先簡(jiǎn)單講一下request這個(gè)東西。
我們先看著這個(gè)URL:http://localhost:8080/web/login.jsp 它是動(dòng)用了8080端口來(lái)進(jìn)行socket通訊的。
我們知道, 通過(guò)
InputStream in = socket.getInputStream() 和
OutputStream out = socket.getOutputStream()
就可以實(shí)現(xiàn)消息的來(lái)來(lái)往往了。
但是如果把Stream給應(yīng)用層看,顯然操作起來(lái)不方便。
所以,在tomcat 的Connector里面, socket被封裝成了Request和Response這兩個(gè)對(duì)象。
我們可以簡(jiǎn)單地把Request看成管發(fā)到服務(wù)器來(lái)的數(shù)據(jù),把Response看成想發(fā)出服務(wù)器的數(shù)據(jù)。
但是這樣又有其他問(wèn)題了??? Request這個(gè)對(duì)象是把socket封裝起來(lái)了, 但是他提供的又東西太多了。
諸如Request.getAuthorization(), Request.getSocket()。 像Authorization這種東西開(kāi)發(fā)人員拿來(lái)基本上用不太著,而像socket這種東西,暴露給開(kāi)發(fā)人員又有潛在的危險(xiǎn)。 而且啊, 在Servlet Specification里面標(biāo)準(zhǔn)的通信類是ServletRequest和HttpServletRequest,而非這個(gè)Request類。 So, So, So. Tomcat必須得搗持搗持Request才行。 最后tomcat選擇了使用搗持模式(應(yīng)該叫適配器模式)來(lái)解決這個(gè)問(wèn)題。它把org.apache.catalina.Request 搗持成了 org.apache.coyote.tomcat4.CoyoteRequest。 而CoyoteRequest又實(shí)現(xiàn)了ServletRequest和HttpServletRequest 這兩種接口。 這樣就提供給開(kāi)發(fā)人員需要且剛剛需要的方法了。
ok, 讓我們?cè)?tomcat的頂層容器 - StandardEngin 的invoke()方法這里設(shè)置一個(gè)斷點(diǎn), 然后訪問(wèn)
http://localhost:8080/web/login.jsp, 我們來(lái)看看在前期都會(huì)路過(guò)哪些地方:
1. run(): 536, java.lang.Thread, Thread.java
CurrentThread
2. run():666, org.apache.tomcat.util.threads.ThreadPool$ControlRunnable, ThreadPool.java
ThreadPool
3. runIt():589, org.apache.tomcat.util.net.TcpWorkerThread, PoolTcpEndpoint.java
ThreadWorker
4. processConnection(): 549
org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler, Http11Protocol.java
http protocol parser
5. Process(): 781, org.apache.coyote.http11.Http11Processor, Http11Processor.java
http request processor
6. service(): 193, org.apache.coyote.tomcat4.CoyoteAdapter,CoyoteAdapter.java
adapter
7. invoke(): 995, org.apache.catalina.core.ContainerBase, ContainerBase.java
StandardEngin
1. 主線程
2. 啟動(dòng)線程池.
3. 調(diào)出線程池里面空閑的工作線程。
4. 把8080端口傳過(guò)來(lái)由httpd協(xié)議封裝的數(shù)據(jù),解析成Request和Response對(duì)象。
5. 使用Http11Processor來(lái)處理request
6. 在Http11Processor里面, 又會(huì)call CoyoteAdapter來(lái)進(jìn)行適配處理,把Request適配成實(shí)現(xiàn)了ServletRequest和HttpServletRequest接口的CoyoteRequest.
7. 到了這里,前期的去毛拔皮工作就基本上搞定,可以交給StandardEngin 做核心的處理工作了。
3. 中期。 在各個(gè)容器間的穿梭。
Request在各個(gè)容器里面的穿梭大致是這樣一種方式:
每個(gè)容器里面都有一個(gè)管道(pipline), 專門用來(lái)傳送Request用的。
管道里面又有好幾個(gè)閥門(valve), 專門用來(lái)過(guò)濾Request用的。
在管道的低部通常都會(huì)放上一個(gè)默認(rèn)的閥們。 這個(gè)閥們至少會(huì)做一件事情,就是把Request交給子容器。
讓我們來(lái)想象一下:
當(dāng)一個(gè)Request進(jìn)入一個(gè)容器后, 它就在管道里面流動(dòng),波羅~ 波羅~ 波羅~ 地穿過(guò)各個(gè)閥門。在流到最后一個(gè)閥門的時(shí)候,吧唧~ 那個(gè)該死的閥門就把它扔給了子容器。 然后又開(kāi)始 波羅~ 波羅~ 波羅~ ... 吧唧~.... 波羅~ 波羅~ 波羅~ ....吧唧~....
就是通過(guò)這種方式, Request 走完了所有的容器。( 感覺(jué)有點(diǎn)像消化系統(tǒng),最后一個(gè)地方有點(diǎn)像那里~ )
OK, 讓我們具體看看都有些什么容器, 各個(gè)容器里面又都有些什么閥門,這些閥們都對(duì)我們的Request做了些什么吧:
3.1 StandardEngin 的pipeline里面放的是:StandardEnginValve
在這里,VALVE做了三件事:
1. 驗(yàn)證傳遞過(guò)來(lái)的request是不是httpservletRequest.
2 驗(yàn)證傳遞過(guò)來(lái)的 request 是否攜帶了host header信息.
3 選擇相應(yīng)的host去處理它。(一般我們都只有一個(gè)host:localhost,也就是127.0.0.1)。
到了這個(gè)地方,我們的request就已經(jīng)完成了在Engin這個(gè)部分的歷史使命,通向前途未卜的下一站: host了。
3.2 StandardHost 的pipline里面放的是: StandardHostValve
1. 驗(yàn)證傳遞過(guò)來(lái)的request是不是httpservletRequest.
2. 根據(jù)Request來(lái)確定哪個(gè)Context來(lái)處理。
Context其實(shí)就是webapp,比如http://localhost:8080/web/login.jsp
這里web就是Context羅!
3. 既然確定了是哪個(gè)Context了,那么就應(yīng)該把那個(gè)Context的classloader付給當(dāng)前線程了。
Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());
這樣request就只看得見(jiàn)指定的context下面的classes啊, jar啊這些,而看不見(jiàn)tomcat本身的類,什么Engin啊, Valve啊。不然還得了??!
4. 既然request到了這里了,看來(lái)用戶是準(zhǔn)備訪問(wèn)web這個(gè)web app了,咋們得更新一下這個(gè)用戶的session不是! Ok , 就由manager更新一下用戶的session信息
5. 交給具體的Context 容器去繼續(xù)處理Request.
6. Context處理完畢了,把classloader還回來(lái)。
3.3 StandardContext 的pipline里面放的是: StandardContextValve
1. 驗(yàn)證傳遞過(guò)來(lái)的request是不是httpservletRequest.
2. 如果request意圖不軌,想要訪問(wèn)/meta-inf, /web-inf這些目錄下的東西,呵呵,沒(méi)有用D!
3. 這個(gè)時(shí)候就會(huì)根據(jù)Request到底是Servlet,還是jsp,還是靜態(tài)資源來(lái)決定到底用哪種Wrapper來(lái)處理這個(gè)Reqeust了。
4. 一旦決定了到底用哪種Wrapper,OK,交給那個(gè)Wrapper處理。
4. 末期。 不同的需求是怎么處理的.
StandardWrapper
之前對(duì)Wrapper沒(méi)有做過(guò)講解,其實(shí)它是這樣一種東西。
我們?cè)谔幚鞷equest的時(shí)候,可以分成3種。
處理靜態(tài)的: org.apache.catalina.servlets.DefaultServlet
處理jsp的:org.apache.jasper.servlet.JspServlet
處理servlet的:org.apache.catalina.servlets.InvokerServlet
不同的request就用這3種不同的servlet去處理。
Wrapper就是對(duì)它們的一種簡(jiǎn)單的封裝,有了Wrapper后,我們就可以輕松地?cái)r截每次的Request。也可以容易地調(diào)用servlet的init()和destroy()方法, 便于管理嘛!
具體情況是這么滴:
如果request是找jsp文件,StandardWrapper里面就會(huì)封裝一個(gè)org.apache.jasper.servlet.JspServlet去處理它。
如果request是找 靜態(tài)資源 ,StandardWrapper里面就會(huì)封裝一個(gè)org.apache.jasper.servlet.DefaultServlet 去處理它。
如果request是找servlet ,StandardWrapper里面就會(huì)封裝一個(gè)org.apache.jasper.servlet.InvokerServlet 去處理它。
StandardWrapper同樣也是容器,既然是容器, 那么里面一定留了一個(gè)管道給request去穿,管道低部肯定也有一個(gè)閥門(注1),用來(lái)做最后一道攔截工作.
在這最底部的閥門里,其實(shí)就主要做了兩件事:
一是啟動(dòng)過(guò)濾器,讓request在N個(gè)過(guò)濾器里面篩一通,如果OK! 那就PASS。 否則就跳到其他地方去了。
二是servlet.service((HttpServletRequest) request,(HttpServletResponse) response); 這個(gè)方法.
如果是 JspServlet, 那么先把jsp文件編譯成servlet_xxx, 再invoke servlet_xxx的servie()方法。
如果是 DefaultServlet, 就直接找到靜態(tài)資源,取出內(nèi)容, 發(fā)送出去。
如果是 InvokerServlet, 就調(diào)用那個(gè)具體的servlet的service()方法。
ok! 完畢。
注1: StandardWrapper 里面的閥門是最后一道關(guān)口了。 如果這個(gè)閥門欲意把request交給StandardWrapper 的子容器處理。 對(duì)不起, 在設(shè)計(jì)考慮的時(shí)候, Wrapper就被考慮成最末的一個(gè)容器, 壓根兒就不會(huì)給Wrapper添加子容器的機(jī)會(huì)! 如果硬是要調(diào)用addChild(), 立馬拋出IllegalArgumentException!
參考:
<http://jakarta.apache.org/tomcat/>
<http://www.onjava.com/pub/a/onjava/2003/05/14/java_webserver.html>