標籤:tomcat 多線程 servlet 讀書筆記 jsp
方法調用序列
展示了方法調用的共同作業圖表:
這個是前面第五章裡,我畫的圖:
我們再回顧一下自從連接器裡
connector.getContainer().invoke(request, response);
這句代碼運行之後發生的事情;
這是第五章的時序圖,放在這一章同樣適用。。。
我們仔細分析一下:
1首先連接器建立請求與響應對象;
2調用這行代碼
connector.getContainer().invoke(request, response)
(我們一StandContext為頂層容器)
3在容器裡會先調用StandardPipeline的invoke方法。如下:
public void invoke(Request request, Response response) throws IOException, ServletException { // Invoke the first Valve in this pipeline for this request (new StandardPipelineValveContext()).invokeNext(request, response); } 4StandardPipeline有個內部類StandardPipelineValveContext,它的invokeNext方法會迴圈管道(StandardContext的管道)裡的所有閥,直到基礎閥,並且調用其invoke方法。基礎閥在StandardContext初始化的時候就預設指定了StandardContextValve();
5基礎閥的invoke方法調用StandardContext的map方法得到Wrapper
6調用得到的wrapper(這裡是StandardWrapper)的invoke方法...
7先重複上面的第三第四步,這時獲得的基礎閥是StandardWrapperValve,它的invoke方法就是調用StandardWrapper類的allocate方法,allocate調用loadServlet獲得servlet。
8產生一個在ApplicationFilterChain:createFilterChain(request, servlet),然後在其internalDoFilter方法中調用servlet的service方法。
SingleThreadModel介面 這個介面能保證,在容器內部,任何時候只有一個進程訪問某個serlvet的service方法。
這個介面是一個空介面,或者叫標誌介面,內部什麼都沒有。就是一種標示而已。
實現此介面的一個servlet通俗稱為SingleThreadModel(STM)的程式組件。
很多程式員以為,只要實現了上述介面就能保證自己的servlet是安全執行緒的。
其實不然,例如如果若干個servlet的service方法都訪問某個靜態類的變數或servelt類以外的類或變數呢?
因而這個介面在Servlet 2.4中就已經廢棄了,就是因為它讓程式員產生了虛假的安全感。
StandardWrapperStandardWrapper的主要作用就是載入它自己所代表的servlet類,並進行執行個體化,但是通過分析上面的調用時序圖,大家知道它是先調用自己的管道,然後是基礎閥,由基礎閥調用StandardWrapper的alloacte方法。
上面已經說了,有個STM,如果容器只是維護一個實現了STM介面的servelt那麼調用的時候就應該是這樣的
Servlet instance = <get an instance of the servlet>; if ((servlet implementing SingleThreadModel>) { synchronized (instance) { instance.service(request, response); } } else { instance.service(request, response); }不過為了保持效能,StandardWrapper一般維護了一個STM servlet 執行個體池。
一個封裝器還負責準備一個 javax.servlet.ServletConfig 的執行個體,這可以在
servlet 內部完成,接下來兩小節討論如何分配和載入 servlet。
Alloacte方法 本方法其實可以分為兩部分:
第一 產生非STMServlet
StandardWrapper 定義一個 java.servlet.Servlet 類型的執行個體
private Servlet instance = null;
方法 allocate 檢查該執行個體是否為 null,如果是調用 loadServlet 方法來載入servlet。然後增加 contAllocated 整型並返回該執行個體。
第二 產生STMServlet
方法 allocate 嘗試返回池中的一個執行個體,變數intancePool是一個java.util.Stack類型的 STM servlet執行個體池。
在這裡我需要給大家解釋三個變數:
countAllocated: 目前存活的servelt數量
the count of allocations that are currently active (even if they are for the same instance, as will be true on a non-STM servlet).
nInstances : 已經載入的serlvet數量
Number of instances currently loaded for a STM servlet.
maxInstances: 這個看名字就知道了,StandardWrapper支援的最大數目的servlet。
上面三個變數肯定把大家搞暈了,但是我想說記著一個StandardWrapper就維護一個servlet,它只有一個class地址,那三個變數是在多線程的時候用的!
這部分的代碼比較麻煩,不管是理解還是說都很麻煩
總而言之,最開始的時候countAllocated與nInstances都是0,先loadServlet()出一個servelt,push到servlet執行個體池裡面,然後取出來..
這部分大家自己看代碼吧(我覺得自己偷的一把好懶呀)
loadServlet方法這個方法首先會回檢查instance,如果不為null,並且當前的StandardWrapper並不表示一個STMServlet類,那麼就直接返回。
另一方面,Catalina也是jsp容器,如果請求的servelt是一個jsp就執行下面的程式碼片段:
String actualClass = servletClass;
if ((actualClass == null) && (jspFile != null)) {
Wrapper jspWrapper = (Wrapper)((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME);
if (jspWrapper != null)
actualClass = jspWrapper.getServletClass();
}
下面就是獲得classloader,預設情況下,我們看前面幾章的內容就知道,在Bootstrap裡我們就已經指定了WebappLoader,通過它,我們可以獲得WebappClassLoader這個對象。
不過在tomcat中,如果要載入的servlet位於org.apache.catalina.下,那麼classLoader就是
classLoader = this.getClass().getClassLoader(); //this 就是StandardWrapper
再下來就是loadClass,接著用Class的newInstance獲得servlet;
下來就是檢查該servlet是否允許載入(這步我看的不是很懂),看它是否實現了ContainerServlet介面。
ContainerServlet介面中有set/getWrapper方法,就是可以讓servlet訪問Catalina的內部功能。
下面就是調用 servlet.init(facade);這個facade是javax.servlet.ServletConfig的一個外觀變數。
如果該StandardWrapper對象表示的是一個STM Servlet,將該執行個體添加到執行個體池中,因此,如果執行個體池如果為null,首先需要建立它。
// Register our newly initialized instance
singleThreadModel = servlet instanceof SingleThreadModel;
if (singleThreadModel) {
if (instancePool == null)
instancePool = new Stack();
}
fireContainerEvent("load", this);
}
最後返回servlet。
ServletConfig對象 上面servlet的init方法的參數實際上就是javax.servlet.ServletConfig介面的執行個體。
問題出現了,這個介面的執行個體從哪來來呢?大家看看StandardWrapper的聲明部分,就知道它本身就實現了ServletConfig介面。
但是在調用init方法是,StandardWrapper並不會直接把自己傳過去而是使用了一個facade,為什麼我主要是直接把StandardWrapper傳過去,那麼StandardWrapper裡面的所有public方法不都暴露了麼?
ServletConfig 介面有以下四個方法getServletContext,getServletName,getInitParameter,和getInitParameterNames。
1 getServletContext
public ServletContext getServletContext() {
if (parent == null)
return (null);
else if (!(parent instanceof Context))
return (null);
else
return (((Context) parent).getServletContext());
}
現在你知道不能單獨部署一個封裝器來表示一個 Servlet,封裝器必須從屬於一個上下文容器,這樣才能使用 ServletConfig 對象使用getServletContext 方法獲得一個 ServletContext 執行個體。
2 getServletName
獲得servlet的名字,沒有什麼好說的
3 getInitParameter
獲得指定的初始參數的值。StandardWrapper中的初始參數放在一個HashMap中:
private HashMap<String, String> parameters = new HashMap<String, String>();
具體的實現大家看代碼,這塊很簡單。
4 getInitParameterNames
返回的是初始化參數的名字的集合,是一個枚舉類。
StandardWrapperFacade類類圖如下:
ServletConfig共有四個方法,facade類getServletName,getInitParameter,getInitParameterNames都是直接調用StandardWrapper,這些都比較簡單,沒有什麼要說的。
不過getServletContext就有點複雜了:
public ServletContext getServletContext() {
ServletContext theContext = config.getServletContext();
if ((theContext != null) &&
(theContext instanceof ApplicationContext))
theContext = ((ApplicationContext) theContext).getFacade();
return (theContext);
}
看到了吧,先調用StandardWrapper獲得Context,但是這裡最後給外面返回的還是Facade。(真tm複雜)。
下面的章節我們會講
StandardWrapperValve,FilterDef,ApplicationFilterConfig,ApplicationFilterChain
how tomcat works 讀書筆記 十一 StandWrapper 上