how tomcat works 讀書筆記 十一 StandWrapper 上
方法調用序列
下圖展示了方法調用的協作圖:
這個是前面第五章裡,我畫的圖:
我們再回顧一下自從連接器裡
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中就已經廢棄了,就是因為它讓程序員產生了虛假的安全感。
StandardWrapper
StandardWrapper的主要作用就是載入它自己所代表的servlet類,並進行實例化,但是通過分析上面的調用時序圖,大家知道它是先調用自己的管道,然後是基礎閥,由基礎閥調用StandardWrapper的alloacte方法。
上面已經說了,有個STM,如果容器只是維護一個實現了STM接口的servelt那麼調用的時候就應該是這樣的
Servlet instance = ;
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
parameters = new HashMap();
具體的實現大家看代碼,這塊很簡單。
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