tomcat(11)org.apache.catalina.core.StandardWrapper源码剖析
生活随笔
收集整理的這篇文章主要介紹了
tomcat(11)org.apache.catalina.core.StandardWrapper源码剖析
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
【0】README
0.0)本文部分文字描述轉自 “how tomcat works”,旨在學習?“tomcat(11)StandardWrapper源碼剖析” 的基礎知識;
0.1)StandardWrapper 是 Catalina中對Wrapper接口的標準實現;要知道,tomcat 中有4種類型的容器:Engine,Host,Context 和 Wrapper;(干貨——review ?tomcat 中有4種類型的容器:Engine,Host,Context 和 Wrapper)
【1】方法調用序列 1)對于每個引入的http 請求,連接器都會調用與其關聯的servlet容器的 invoke() 方法。然后,servlet容器會調用其所有子容器的invoke() 方法; 2)下圖展示了連接器接收到http 請求后的方法調用的協作圖;
3)上圖的具體steps 如下: step1)連接器創建 request 和 response對象; step2)連接器調用StandardContext.invoke()方法; step3)StandardContext.invoke()方法調用其管道的invoke() 方法。StandardContext的管道對象的基礎閥是 StandardCoantextValve類的實例,因此, StandardContext 的管道會調用 StandardContextValve.invoke()方法; step4)StandardContextValve.invoke()方法 獲取相應的Wrapper 實例處理 http請求,調用Wrapper實例的invoke()方法; step5)StandardWrapper類是Wrapper接口的標準實現,StandardWrapper.invoke()方法 會調用其管道對象的invoke()方法; step6)StandardWrapper的管道對象中的基礎閥是 StandardWrapperValve 類的實例,因此,會調用StandardWrapperValve.invoke()方法,StandardWrapperValve.invoke()方法會調用Wrapper實例的 allocate() 方法獲取servlet實例; step7)allocate()方法調用load() 方法載入相應的servlet類,若已經載入,則無需重復載入; step8)load()方法調用servlet實例的init()方法; step9)StandardWrapperValve調用servlet.service()方法; // Call the filter chain for this request// NOTE: This also calls the servlet's service() methodtry { // org.apache.catalina.core.StandardWrapperValve.invoke()String jspFile = wrapper.getJspFile();if (jspFile != null)sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);elsesreq.removeAttribute(Globals.JSP_FILE_ATTR);if ((servlet != null) && (filterChain != null)) {filterChain.doFilter(sreq, sres); // highlight line. doFilter() calls servlet.service()}sreq.removeAttribute(Globals.JSP_FILE_ATTR);} Attention)StandardContext類的構造函數會設置StandardContextValve類的一個實例作為其基礎閥;? public StandardContext() { // org.apache.catalina.core.StardardContextsuper();pipeline.setBasic(new StandardContextValve());namingResources.setContainer(this); } Attention)StandardWrapper類的構造函數也會設置一個 StandardWrapperValve實例作為其基礎閥: public StandardWrapper() { // org.apache.catalina.core.StardardWrappersuper();swValve=new StandardWrapperValve();pipeline.setBasic(swValve); } 4)依據上述(3)小節中的 “處理http 請求的方法調用協作圖”,本文按照慣例給出了具體的調用過程,如下:
4.1)本文第一張是借用了 “tomcat(10)安全性中章節【6.4】中Supplement-補充模塊”的第2張圖;(for spec info,please visit tomcat(10)安全性),這旨在說明從HttpConnector -> StandardContext.invoke() -> StandardPipeline.invoke()的調用過程;
4.2)本文接著上面的調用過程繼續分析,調用過程如下圖;旨在說明? StandardPipeline.invoke() -> StandardContextValve.invoke() -> StandardWrapper.invoke() -> StandardPipeline.invoke() -> StandardWrapperValve.invoke() -> ApplicationFilterChain().doFilter()? ->?ApplicationFilterChain().internalDoFilter() -> HttpServlet(ModernServlet).service() -> ModernServlet->doGet() 的調用過程.(Bingo)
對上述協作圖和詳細調用過程圖的分析(Analysis): A0)要知道Tomcat中有4種容器:Engine,Host,Context 和 Wrapper;(干貨——本文一直強調這一點,理解容器的層次結構對于理解tomcat非常重要) A1)StandardContext 和 StandardWrapper 都是容器:他們都繼承自 ContainerBase,只不過StandardWrapper是StandardContext的子容器,而StandardWrapper是最小的容器,即它沒有子容器; A2)下面分別看StandardWrapper,StandardContext的構造函數?和 ContainerBase 的變量定義; public final class StandardWrapper extends ContainerBase implements ServletConfig, Wrapper {public StandardWrapper() {super();swValve=new StandardWrapperValve();pipeline.setBasic(swValve);} } public class StandardContext extends ContainerBase implements Context {public StandardContext() { super();pipeline.setBasic(new StandardContextValve());namingResources.setContainer(this);} } public abstract class ContainerBase implements Container, Lifecycle, Pipeline {protected Pipeline pipeline = new StandardPipeline(this); // highlight line.protected HashMap children = new HashMap();protected int debug = 0; protected LifecycleSupport lifecycle = new LifecycleSupport(this); protected ArrayList listeners = new ArrayList(); protected Loader loader = null; protected Logger logger = null; protected Manager manager = null; protected Cluster cluster = null; protected Mapper mapper = null; protected HashMap mappers = new HashMap(); protected String mapperClass = null; protected String name = null; protected Container parent = null; protected ClassLoader parentClassLoader = null; protected Pipeline pipeline = new StandardPipeline(this); protected Realm realm = null; protected DirContext resources = null; protected static StringManager sm = StringManager.getManager(Constants.Package); protected boolean started = false; protected PropertyChangeSupport support = new PropertyChangeSupport(this); } A3)可以看到 父容器ContainerBase定義了管道StandardPipeline,而子容器StandardContext 設置StandardContextValve為基礎閥;而最小的容器StandardWrapper設置StandardWrapperValve為基礎閥; A4)也即 StandardContext 和?StandardWrapper 共用同一個管道,分別設置不同的基礎閥;(當然,可以分別設置非基礎閥,非基礎閥在基礎閥被調用之前調用);
【2】SingleThreadModel(已經被棄用了) 1)intro:servlet類可以實現 javax.servlet.SingleThreadModel 接口,這樣的servlet類也稱為 SingleThreadModel(STM)servlet類。根據servlet規范,實現此接口的目的是保證 servlet實例一次只處理一個請求; Attention)若 servlet類實現 SingleThreadModel接口,則可以保證絕不會有兩個線程同時執行該servlet.service()方法。這一點由 servlet容器通過控制對單一 servlet實例的同步訪問實現,或者維護一個 servlet實例池,然后將每個新請求分派給一個空閑的servlet實例。該接口并不能防止servlet訪問共享資源造成的同步問題,例如訪問類的靜態變量或訪問servlet作用域之外的類; (干貨——有很多程序員哥哥沒有讀懂這段話,想當然的認為,實現了該接口的servlet就是線程安全的。這種想法是錯誤的,請再度一遍上面的引文內容(原文作者說的,哈哈))
2)事實上,實現了 SingleThreadModel 接口的servlet類只能保證在同一時刻,只有一個線程在執行該 servlet實例的service()方法。但,為了提高執行 性能,servlet容器會創建多個STM servlet實例。也就是說,STM servlet.service()方法 會在多個STM?servlet實例中并發執行。如果servlet實例需要靜態類變量或類外的某些資源的話,就有可能引起同步問題; Atttention)在servlet 2.4中,SingleThreadModel接口已經被棄用了,因為它會使 servlet程序員誤以為該接口的servlet類就是多線程安全的;
【3】StandardWrapper 1)intro to?StandardWrapper:其主要任務是 載入它所代表的servlet類,并進行實例化; 2)StandardWrapper并不調用servlet的service方法,該任務由 StandardWrapperValve對象(StandardWrapper實例的管道對象中的基礎閥)完成; 3)StandardWrapperValve對象通過調用allocate()方法從 StandardWrapper實例中獲取servlet實例,在獲得servlet實例后,StandardWrapperValve實例就會調用servlet實例的service()方法; 【3.1】分配servlet實例? 1)分配servlet實例是由 StandardWrapper.allocate()方法來完成的(allocate方法返回請求的servelt實例); 2)allocate()方法分為兩部分(parts): p1)第一部分:?allocate()首先檢查 instance是否為null,若是, 則allocate()方法調用 loadServlet()方法載入相關的servlet類,然后 整型變量countAllocated加1,返回instance的值; p2)第二部分: p2.1)若StandardWrapper表示的servlet是一個STM servlet類,則allocate()會試圖從對象池中返回一個servlet實例。變量 instancePool 是一個 java.util.Stack類型的棧,其中保存了所有的STM servlet實例: private Stack instancePool = null; p2.2)只要STM servlet實例數不超過指定的最大值,allocate()方法會返回一個 STM servlet實例。整型變量maxInstances 保存了在棧中存儲的 STM servlet實例的最大值,default value = 20; private int maxInstances = 20; p2.3)而 nInstances 保存了當前 STM servlet實例的數量(初始為0);
step1)檢查當前的StandardWrapper類是否表示的是一個 STM servlet類,若不是,且變量instance不為null(表示以前已經載入過這個servlet),它就直接返回該實例; // Nothing to do if we already have an instance or an instance poolif (!singleThreadModel && (instance != null))return instance; step2)獲得 System.out 和 System.err 的輸出,便于它使用 javax.servlet.ServletConfig.log() 方法記錄日志消息: PrintStream out = System.out;if (swallowOutput) {SystemLogHandler.startCapture();} step3)定義類型為javax.servlet.Servlet 名為servlet 的變量,其表示已載入的servlet實例,會由 loadServlet()方法返回; Servlet servlet = null; step4)由于Catalina是一個JSP容器,故loadServlet()方法必須檢查請求的servlet是不是一個jsp 頁面。若是,則loadServlet() 方法需要獲取代表該jsp 頁面的實際servlet類; String actualClass = servletClass;if ((actualClass == null) && (jspFile != null)) {Wrapper jspWrapper = (Wrapper)((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME);if (jspWrapper != null)actualClass = jspWrapper.getServletClass(); } // public static final String JSP_SERVLET_NAME = "jsp";<span style="font-family: SimSun; line-height: 1.5; background-color: inherit;">? ? ? ? ? ?</span> step5)如果找不到該jsp 頁面的servlet類,則會使用變量 servletClass(actualClass)的值。若沒有調用StandardWrapper.serServletClass() 方法設置servletClass的值,則會拋出異常,并停止執行后續方法; // Complain if no servlet class has been specifiedif (actualClass == null) {unavailable(null);throw new ServletException(sm.getString("standardWrapper.notClass", getName()));}<span style="font-family: SimSun; background-color: rgb(255, 255, 255);">? ? ? ? ? ?</span> step6)這時,要載入的servlet類名已經解析完了,loadServlet()方法會獲取載入器 Loader loader = getLoader();public Loader getLoader() { // org.apache.catalina.core.ContainerBase.getLoader();if (loader != null)return (loader);if (parent != null)return (parent.getLoader());return (null);} step7)若找到載入器(loader),則loadServlet()方法調用getClassLoader()方法獲取一個ClassLoader; ClassLoader classLoader = loader.getClassLoader(); step8)Catalina提供了一些用于訪問servlet容器內部數據的專用servlet類。如果某個servlet類是這種專用的servlet,即若isContainerProvidedServlet()方法返回true,則變量 classLoader被賦值為另一種ClassLoader實例,如此一來,這個servlet實例就可以訪問Catalina的內部數據了; // Special case class loader for a container provided servletif (isContainerProvidedServlet(actualClass)) {classLoader = this.getClass().getClassLoader();log(sm.getString("standardWrapper.containerServlet", getName()));} step9)準備好類載入器和準備載入的servlet類名后,loadServlet()方法就可以載入servlet類了; // Load the specified servlet class from the appropriate class loaderClass classClass = null;try {if (classLoader != null) {classClass = classLoader.loadClass(actualClass);} else {classClass = Class.forName(actualClass);}} catch (ClassNotFoundException e) {unavailable(null);throw new ServletException(sm.getString("standardWrapper.missingClass", actualClass),e);} if (classClass == null) {unavailable(null);throw new ServletException(sm.getString("standardWrapper.missingClass", actualClass));} step10)實例化該servlet ? ? ? // Instantiate and initialize an instance of the servlet class itselftry {servlet = (Servlet) classClass.newInstance();} catch (ClassCastException e) {unavailable(null);// Restore the context ClassLoaderthrow new ServletException(sm.getString("standardWrapper.notServlet", actualClass), e);} catch (Throwable e) {unavailable(null);// Restore the context ClassLoaderthrow new ServletException(sm.getString("standardWrapper.instantiate", actualClass), e);}
step11)在loadServlet()方法實例化這個servlet之前,它會調用 isServletAllowed()方法檢查該servlet 類是否允許載入:? ? ? // Check if loading the servlet in this web application should be// allowedif (!isServletAllowed(servlet)) {throw new SecurityException(sm.getString("standardWrapper.privilegedServlet",actualClass));}
step12)若通過了安全檢查,它還會繼續檢查該servlet類是否是一個 ContainerServlet類型的servlet(實現了 org.apache.catalina.ContainerServlet接口的 servlet可以訪問Catalina的內部功能)。若該servlet類是一個 ContainerServlet,loadServlet()方法會調用 ContainerServlet.setWrapper(),傳入StandardWrapper實例;? // Special handling for ContainerServlet instancesif ((servlet instanceof ContainerServlet) &&isContainerProvidedServlet(actualClass)) {((ContainerServlet) servlet).setWrapper(this);} step13)觸發BEFORE_INIT_EVENT事件,調用servlet實例的 init()方法(init()方法傳入了javax.servlet.ServletConfig外觀對象):? // Call the initialization method of this servlettry {instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet);servlet.init(facade); // highlight line. step14)若變量 loadOnStartup 大于0, 且被請求的servlet類實際上是一個jsp 頁面,則servlet實例的service()方法;? if ((loadOnStartup >= 0) && (jspFile != null)) {// Invoking jspInitHttpRequestBase req = new HttpRequestBase();HttpResponseBase res = new HttpResponseBase();req.setServletPath(jspFile);req.setQueryString("jsp_precompile=true");servlet.service(req, res); // highlight line.}instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet); step15)觸發AFTER_INIT_EVENT事件 instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, step16)若StandardWrapper對象表示的servlet類是一個STM servlet,則將該servlet實例添加到servlet實例池中。因此會判斷 instancePool 是否為null,若是,則要給他賦值一個Stack 對象;? // Register our newly initialized instancesingleThreadModel = servlet instanceof SingleThreadModel;if (singleThreadModel) {if (instancePool == null)instancePool = new Stack(); // highlight line. } step17)在finally代碼塊中,loadServlet()方法停止捕獲System.out 和 System.err 對象,記錄在載入 ServletContext.log()方法的過程中產生的日志消息; finally {if (swallowOutput) {String log = SystemLogHandler.stopCapture();if (log != null && log.length() > 0) {if (getServletContext() != null) {getServletContext().log(log); // highlight line.} else {out.println(log);}}}} public ServletContext getServletContext() { org.apache.catalina.core.StandardWrapper.getServletContext()if (parent == null)return (null);else if (!(parent instanceof Context))return (null);elsereturn (((Context) parent).getServletContext());} step18)最后返回已載入的servlet實例; return servlet; 【3.3】ServletConfig對象 1)intro:在上述step13)中提到了 servlet.init(facade),而facade 是 javax.servlet.ServletConfig對象的一個外觀變量; 2)StandardWrapper對象是如何獲取 servletConfig 對象的?答案就在 StandardWrapper中,該類不僅實現了 Wrapper接口,還實現了 javax.servlet.ServletConfig 接口; public final class StandardWrapper extends ContainerBase implements ServletConfig, Wrapper { // org.apache.catalina.core.StandardWrapper // ...... } public interface ServletConfig { // javax.servlet.ServletConfig public String getServletName();public ServletContext getServletContext(); public String getInitParameter(String name);public Enumeration getInitParameterNames(); } 3)javax.servlet.ServletConfig 接口有4個方法:getServletContext() , getServletName(), ?getInitParameter(), ?getInitParameterNames()方法;下面對這4個方法進行說明; method1)getServletConfig()方法: public ServletContext getServletContext() { // org.apache.catalina.core.StandardWrapper.getServletContext()if (parent == null)return (null);else if (!(parent instanceof Context))return (null);elsereturn (((Context) parent).getServletContext());}/*** Return the servlet context for which this Context is a facade.*/public ServletContext getServletContext() { // org.apache.catalina.core.StandardContext.getServletContext()if (context == null)context = new ApplicationContext(getBasePath(), this);return (context);} Attention)正如以上代碼所展示的那樣,無法單獨使用一個Wrapper實例來表示一個 servlet 類的定義。Wrapper 實例必須駐留在某個 Context 容器中,這樣,當調用其父容器的getServletConfig()方法時,才能返回ServletContext類的一個實例; method2)getServletName()方法:該方法返回 servlet類的名字,該方法的簽名如下: ?public String getServletName() { // org.apache.catalina.core.StandardWrapper.getServletName()return (getName());} public String getName() { // org.apache.catalina.core.ContainerBase.getName(). // 因為 public final class StandardWrapper extends ContainerBasereturn (name);} method3)getInitParameter()方法:該方法返回指定初始參數的值 public String getInitParameter(String name) { // org.apache.catalina.core.StandardWrapper.getInitParameter()return (findInitParameter(name));} public String findInitParameter(String name) { // org.apache.catalina.core.StandardWrapper.findInitParameter()synchronized (parameters) {return ((String) parameters.get(name));}} 對getInitParameter()方法的分析(Analysis): A1)在StandardWrapper類中,初始化參數 parameters 存儲在一個 HashMap類型中; private HashMap parameters = new HashMap(); A2)通過addInitParameter()方法,傳入參數的名字 和 對應的值 來填充變量 parameters 的值: public void addInitParameter(String name, String value) { // org.apache.catalina.core.StandardWrapper.addInitParameter().synchronized (parameters) {parameters.put(name, value);}fireContainerEvent("addInitParameter", name); // highlight line.} public void fireContainerEvent(String type, Object data) {// org.apache.catalina.core.ContainerBase.fireContainerEvent().if (listeners.size() < 1)return;ContainerEvent event = new ContainerEvent(this, type, data);ContainerListener list[] = new ContainerListener[0];synchronized (listeners) {list = (ContainerListener[]) listeners.toArray(list);}for (int i = 0; i < list.length; i++)((ContainerListener) list[i]).containerEvent(event);} A3)StandardWrapper.getInitParameter()方法的實現如下:? public String getInitParameter(String name) {return (findInitParameter(name));} A4)findInitParameter()方法接收一個指定的初始化參數名的字符串變量,調用HashMap 變量 parameters的get()方法獲取初始化參數的值; public String findInitParameter(String name) { // org.apache.catalina.core.StandardWrapper.findInitParameter()synchronized (parameters) {return ((String) parameters.get(name)); // highlight line.}} method4)getInitParameterNames()方法: 該方法返回所有初始化參數的名字的集合,實際上是 java.util.Enumeration的實例;? public Enumeration getInitParameterNames() {synchronized (parameters) {return (new Enumerator(parameters.keySet()));}} 【3.4】servlet容器的父子關系 1)intro to StandardWrapper:Wrapper實例代表一個servlet實例,是最低級的容器,故Wrapper不能再有子容器,不應該調用addChild()方法添加子容器,否則拋出 java.lang.IllegalStateException 異常;(干貨review——Wrapper實例代表一個servlet實例,是最低級的容器,故Wrapper不能再有子容器) 2)org.apache.catalina.core.StandardWrapper.addChild()方法實現如下: public void addChild(Container child) {throw new IllegalStateException (sm.getString("standardWrapper.notChild"));} Attention)Wrapper容器的父容器只能是 Context 容器;若我們在設置父容器的時候,傳入了非Context容器,則拋出 java.lang.IllegalArgumentException 異常; public void setParent(Container container) { // org.apache.catalina.core.StandardWrapper.setParent().if ((container != null) && !(container instanceof Context)) throw new IllegalArgumentException(sm.getString("standardWrapper.notContext"));if (container instanceof StandardContext) {swallowOutput = ((StandardContext)container).getSwallowOutput();}super.setParent(container); // highlight line.} public void setParent(Container container) { // org.apache.catalina.core.ContainerBase.setParent().Container oldParent = this.parent;this.parent = container;support.firePropertyChange("parent", oldParent, this.parent);}
【4】 StandardWrapperFacade類?(干貨——應用了設計模式中的外觀模式) 1)problem+solution: 1.1)problem:StandardWrapper實例會調用它所載入的servlet類的實例的init()方法。init()方法需要一個javax.servlet.ServletConfig 實例,而StandardWrapper了本身也實現了 javax.servlet.ServletConfig 接口,所以,理論上 StandardWrapper需要將其中大部分公共方法對servlet程序員隱藏起來; 1.2)solution:為了實現這個目的,StandardWrapper類將自身實例包裝成 StandardWrapperFacade類的一個實例;
2)StandardWrapper類創建StandardWrapperFacade對象,并將自身作為參數傳入StandardWrapperFacade的構造器; private StandardWrapperFacade facade = new StandardWrapperFacade(this); // defined in StandardWrapper.java 3)StandardWrapperFacade的構造函數; public StandardWrapperFacade(StandardWrapper config) {super();this.config = (ServletConfig) config; // private ServletConfig config = null;} 4)因此當創建StandardWrapper對象調用servlet實例的 init()方法時,它會傳入StandardWrapperFacade類的一個實例。這樣,在servlet實例內調用 ServletConfig.getServletName(),ServletConfig.getInitParameter(), getInitParameterNames() ,getServletContext()方法會直接傳遞給 StandardWrapper類的相應方法; 5)org.apache.catalina.core.StandardWrapperFacade 的定義如下: public final class StandardWrapperFacade implements ServletConfig {public StandardWrapperFacade(StandardWrapper config) {super();this.config = (ServletConfig) config;}public String getServletName() {return config.getServletName();}public ServletContext getServletContext() {ServletContext theContext = config.getServletContext();if ((theContext != null) &&(theContext instanceof ApplicationContext))theContext = ((ApplicationContext) theContext).getFacade();return (theContext);}public String getInitParameter(String name) {return config.getInitParameter(name);}public Enumeration getInitParameterNames() {return config.getInitParameterNames();} }
【5】StandardWrapperValve類 1)StandardWrapperValve類是 StandardWrapper實例中的基礎閥,要完成兩個操作(Operations): public StandardWrapper() { // StandardWrapper的構造函數;super();swValve=new StandardWrapperValve();pipeline.setBasic(swValve);} O1)執行與該servlet實例關聯的全部過濾器;(干貨——這里引入了過濾器) O2)調用servlet實例的service()方法; 2)完成上述任務后,在 StandardWrapperValve.invoke()方法實現中會執行以下操作(Operations): O1)調用StandardWrapper.allocate()方法獲取該StandardWrapper實例所表示的 servlet實例; public void invoke(Request request, Response response,ValveContext valveContext)throws IOException, ServletException {long t1=System.currentTimeMillis();requestCount++;// Initialize local variables we may needboolean unavailable = false;Throwable throwable = null;StandardWrapper wrapper = (StandardWrapper) getContainer();ServletRequest sreq = request.getRequest();ServletResponse sres = response.getResponse();Servlet servlet = null;HttpServletRequest hreq = null;if (sreq instanceof HttpServletRequest)hreq = (HttpServletRequest) sreq;HttpServletResponse hres = null;if (sres instanceof HttpServletResponse)hres = (HttpServletResponse) sres;// Check for the application being marked unavailableif (!((Context) wrapper.getParent()).getAvailable()) {hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,sm.getString("standardContext.isUnavailable"));unavailable = true;}// Check for the servlet being marked unavailableif (!unavailable && wrapper.isUnavailable()) {log(sm.getString("standardWrapper.isUnavailable",wrapper.getName()));if (hres == null) {; // NOTE - Not much we can do generically} else {long available = wrapper.getAvailable();if ((available > 0L) && (available < Long.MAX_VALUE))hres.setDateHeader("Retry-After", available);hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,sm.getString("standardWrapper.isUnavailable",wrapper.getName()));}unavailable = true;}// Allocate a servlet instance to process this requesttry {if (!unavailable) {servlet = wrapper.allocate(); // highlight line.} // ...... O2)調用私有方法 createFilterChain(),創建過濾器鏈; // Create the filter chain for this request ApplicationFilterChain filterChain = createFilterChain(request, servlet); // for create FilterChain方法,本章節末尾; private ApplicationFilterChain createFilterChain(Request request, Servlet servlet) { if (servlet == null)return (null);ApplicationFilterChain filterChain = new ApplicationFilterChain(); filterChain.setServlet(servlet);StandardWrapper wrapper = (StandardWrapper) getContainer();filterChain.setSupport(wrapper.getInstanceSupport());// Acquire the filter mappings for this ContextStandardContext context = (StandardContext) wrapper.getParent();FilterMap filterMaps[] = context.findFilterMaps();// If there are no filter mappings, we are doneif ((filterMaps == null) || (filterMaps.length == 0))return (filterChain);// Acquire the information we will need to match filter mappingsString requestPath = null;if (request instanceof HttpRequest) {HttpServletRequest hreq =(HttpServletRequest) request.getRequest();String contextPath = hreq.getContextPath();if (contextPath == null)contextPath = "";String requestURI = ((HttpRequest) request).getDecodedRequestURI();if (requestURI.length() >= contextPath.length())requestPath = requestURI.substring(contextPath.length());}String servletName = wrapper.getName();int n = 0;// Add the relevant path-mapped filters to this filter chainfor (int i = 0; i < filterMaps.length; i++) {if (!matchFiltersURL(filterMaps[i], requestPath))continue;ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig == null) {continue;}filterChain.addFilter(filterConfig);n++;}// Add filters that match on servlet name secondfor (int i = 0; i < filterMaps.length; i++) {if (!matchFiltersServlet(filterMaps[i], servletName))continue;ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig == null) {continue;}filterChain.addFilter(filterConfig);n++;}return (filterChain);} O3)調用過濾器鏈的 doFilter()方法,其中包括調用servlet實例的service()方法; try {String jspFile = wrapper.getJspFile();if (jspFile != null)sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);elsesreq.removeAttribute(Globals.JSP_FILE_ATTR);if ((servlet != null) && (filterChain != null)) {filterChain.doFilter(sreq, sres); // hightlight line.}sreq.removeAttribute(Globals.JSP_FILE_ATTR); // ......public void doFilter(ServletRequest request, ServletResponse response) //org.apache.catlina.core.ApplicationFilterChain.doFileter()throws IOException, ServletException {if( System.getSecurityManager() != null ) {final ServletRequest req = request;final ServletResponse res = response;try {java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction(){public Object run() throws ServletException, IOException {internalDoFilter(req,res); // highlight line. internalDoFilter() 參見文末.return null;}});} catch( PrivilegedActionException pe) {Exception e = pe.getException();if (e instanceof ServletException)throw (ServletException) e;else if (e instanceof IOException)throw (IOException) e;else if (e instanceof RuntimeException)throw (RuntimeException) e;elsethrow new ServletException(e.getMessage(), e);}} else {internalDoFilter(request,response);}} private void internalDoFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException { //org.apache.catalina.core.ApplicationFilterChain.internalDoFilter().// Construct an iterator the first time this method is calledif (this.iterator == null)this.iterator = filters.iterator();// Call the next filter if there is oneif (this.iterator.hasNext()) {ApplicationFilterConfig filterConfig =(ApplicationFilterConfig) iterator.next();Filter filter = null;try {filter = filterConfig.getFilter();support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,filter, request, response);filter.doFilter(request, response, this);support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,filter, request, response);} //......return;}// We fell off the end of the chain -- call the servlet instancetry {support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,servlet, request, response);if ((request instanceof HttpServletRequest) &&(response instanceof HttpServletResponse)) { servlet.service((HttpServletRequest) request, (HttpServletResponse) response); // 這不就是你夢寐以求的service()方法嗎?哈哈。} else {servlet.service(request, response); // and this highlight line.}support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,servlet, request, response);} //......} O4)釋放過濾器鏈; try {if (filterChain != null)filterChain.release(); // highlight line.} catch (Throwable e) {log(sm.getString("standardWrapper.releaseFilters",wrapper.getName()), e);if (throwable == null) {throwable = e;exception(request, response, e);}} void release() { //org.apache.catalina.core.ApplicationFilterChain.release()this.filters.clear();this.iterator = iterator;this.servlet = null;} O5)調用Wrapper實例的 deallocate()方法; // Deallocate the allocated servlet instancetry {if (servlet != null) {wrapper.deallocate(servlet); // highlight line.}} catch (Throwable e) {log(sm.getString("standardWrapper.deallocateException",wrapper.getName()), e);if (throwable == null) {throwable = e;exception(request, response, e);}}public void deallocate(Servlet servlet) throws ServletException { //org.apache.catalina.core.StandardWrapper.deallocate()// If not SingleThreadModel, no action is requiredif (!singleThreadModel) {countAllocated--;return;}synchronized (instancePool) {countAllocated--;instancePool.push(servlet);instancePool.notify();}} O6)若該servlet類再也不會被使用到,調用Wrapper實例的unload()方法; // If this servlet has been marked permanently unavailable,// unload it and release this instancetry {if ((servlet != null) &&(wrapper.getAvailable() == Long.MAX_VALUE)) {wrapper.unload(); // highlight line.}} // ......long t2=System.currentTimeMillis();long time=t2-t1;processingTime+=time;if( time > maxTime ) maxTime=time;} Attention)以上調用過程中,最重要的是對 createFilterChain()方法和過濾器鏈的 doFilter()方法的調用。createFilterChain()方法創建一個 ApplicationFilterChain實例,并將所有需要應用到該Wrapper實例所代表的servlet實例的過濾器添加到其中; private ApplicationFilterChain createFilterChain(Request request,Servlet servlet) {if (servlet == null)return (null);ApplicationFilterChain filterChain =new ApplicationFilterChain();filterChain.setServlet(servlet);StandardWrapper wrapper = (StandardWrapper) getContainer();filterChain.setSupport(wrapper.getInstanceSupport());StandardContext context = (StandardContext) wrapper.getParent();FilterMap filterMaps[] = context.findFilterMaps();if ((filterMaps == null) || (filterMaps.length == 0))return (filterChain);// Acquire the information we will need to match filter mappingsString requestPath = null;if (request instanceof HttpRequest) {HttpServletRequest hreq =(HttpServletRequest) request.getRequest();String contextPath = hreq.getContextPath();if (contextPath == null)contextPath = "";String requestURI = ((HttpRequest) request).getDecodedRequestURI();if (requestURI.length() >= contextPath.length())requestPath = requestURI.substring(contextPath.length());}String servletName = wrapper.getName();int n = 0; // Add the relevant path-mapped filters to this filter chainfor (int i = 0; i < filterMaps.length; i++) {if (!matchFiltersURL(filterMaps[i], requestPath)) continue;ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig == null) {continue; }filterChain.addFilter(filterConfig); n++;}// Add filters that match on servlet name secondfor (int i = 0; i < filterMaps.length; i++) {if (!matchFiltersServlet(filterMaps[i], servletName)) continue;ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig == null) {continue;}filterChain.addFilter(filterConfig);n++;}return (filterChain); } public synchronized void unload() throws ServletException {if (!singleThreadModel && (instance == null))return;unloading = true;if (countAllocated > 0) {int nRetries = 0;while (nRetries < 10) {if (nRetries == 0) {log("Waiting for " + countAllocated +" instance(s) to be deallocated");}try {Thread.sleep(50);} catch (InterruptedException e) {;}nRetries++;}}ClassLoader oldCtxClassLoader =Thread.currentThread().getContextClassLoader();ClassLoader classLoader = instance.getClass().getClassLoader();PrintStream out = System.out;if (swallowOutput) {SystemLogHandler.startCapture();}try {instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_DESTROY_EVENT, instance);Thread.currentThread().setContextClassLoader(classLoader);instance.destroy();instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_DESTROY_EVENT, instance);} catch (Throwable t) {instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_DESTROY_EVENT, instance, t);instance = null;instancePool = null;nInstances = 0;fireContainerEvent("unload", this);unloading = false;throw new ServletException(sm.getString("standardWrapper.destroyException", getName()),t);} finally {Thread.currentThread().setContextClassLoader(oldCtxClassLoader);if (swallowOutput) {String log = SystemLogHandler.stopCapture();if (log != null && log.length() > 0) {if (getServletContext() != null) {getServletContext().log(log);} else {out.println(log);}}}}instance = null;if (singleThreadModel && (instancePool != null)) {try {Thread.currentThread().setContextClassLoader(classLoader);while (!instancePool.isEmpty()) {((Servlet) instancePool.pop()).destroy();}} catch (Throwable t) {instancePool = null;nInstances = 0;unloading = false;fireContainerEvent("unload", this);throw new ServletException(sm.getString("standardWrapper.destroyException",getName()), t);} finally {Thread.currentThread().setContextClassLoader(oldCtxClassLoader);}instancePool = null;nInstances = 0;}singleThreadModel = false;unloading = false;fireContainerEvent("unload", this); }
【6】 FilterDef類(org.apache.catalina.deploy.FilterDef) 1)intro:FilterDef 是一個過濾器的定義; 2)FilterDef類中的每個屬性表示在定義filter元素時聲明的子元素。其中Map 類型的變量parameters 存儲了初始化過濾器時所需要的所有參數。addInitParameter()方法用于向parameters 中添加新的 name/value 形式的參數名和對應的值; 3)其定義源碼如下: public final class FilterDef { // org.apache.catalina.deploy.FilterDef private String description = null;public String getDescription() {return (this.description);}public void setDescription(String description) {this.description = description;} private String displayName = null;public String getDisplayName() {return (this.displayName);}public void setDisplayName(String displayName) {this.displayName = displayName;}private String filterClass = null;public String getFilterClass() {return (this.filterClass);}public void setFilterClass(String filterClass) {this.filterClass = filterClass;} private String filterName = null;public String getFilterName() {return (this.filterName);}public void setFilterName(String filterName) {this.filterName = filterName;} private String largeIcon = null;public String getLargeIcon() {return (this.largeIcon);}public void setLargeIcon(String largeIcon) {this.largeIcon = largeIcon;} private Map parameters = new HashMap();public Map getParameterMap() {return (this.parameters);}private String smallIcon = null;public String getSmallIcon() {return (this.smallIcon);}public void setSmallIcon(String smallIcon) {this.smallIcon = smallIcon;} public void addInitParameter(String name, String value) {parameters.put(name, value);}public String toString() {StringBuffer sb = new StringBuffer("FilterDef[");sb.append("filterName=");sb.append(this.filterName);sb.append(", filterClass=");sb.append(this.filterClass);sb.append("]");return (sb.toString());} }
【7】ApplicationFilterConfig類(org.apache.catalina.core.ApplicationFilterConfig-應用過濾器配置類) 1)intro:ApplicationFilterConfig類實現了 javax.servlet.FilterConfig接口,該類用于管理web 應用程序第1次啟動時創建的所有過濾器實例; 2)類簽名:final class ApplicationFilterConfig implements FilterConfig? 3)可以通過把一個 org.apache.catalina.Context對象和 一個 FilterDef對象傳遞給 ApplicationFilterConfig類的構造函數來創建一個 ApplicationFilterConfig對象:? public ApplicationFilterConfig(Context context, FilterDef filterDef)throws ClassCastException, ClassNotFoundException,IllegalAccessException, InstantiationException,ServletException {super();this.context = context;setFilterDef(filterDef);} 對以上代碼的分析(Analysis): A1)Context對象表示一個web 應用程序; A2)FilterDef對象表示一個過濾器的定義; 4)ApplicationFilterConfig.getFilter()方法:會返回一個 javax.servlet.Filter對象,該方法負責載入并實例化一個過濾器類;? public String getFilterName() { // org.apache.catalina.core.ApplicationFilterConfig.getFilterName().return (filterDef.getFilterName()); }
【8】ApplicationFilterChain類(org.apache.catalina.core.ApplicationFilterChain) 1)intro:?ApplicationFilterChain類實現了 javax.servlet.FilterChain接口,StandardWrapperValve.invoke() 方法會創建 ApplicationFilterChain類的一個實例,并調用其 doFilter()方法; 2)Filter接口的doFilter()方法的簽名如下: public interface Filter { // javax.servlet.Filterpublic void init(FilterConfig filterConfig) throws ServletException; public void doFilter ( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException; public void destroy(); }3)ApplicationFilterChain.doFilter()方法會將?ApplicationFilterChain 類自身作為第3個參數傳遞給過濾器的 doFilter()方法; public void doFilter(ServletRequest request, ServletResponse response) //org.apache.catalina.ApplicationFilterChain.doFileter().throws IOException, ServletException {if( System.getSecurityManager() != null ) {final ServletRequest req = request;final ServletResponse res = response;try {java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction(){public Object run() throws ServletException, IOException {internalDoFilter(req,res);return null;}});} catch( PrivilegedActionException pe) {Exception e = pe.getException();if (e instanceof ServletException)throw (ServletException) e;else if (e instanceof IOException)throw (IOException) e;else if (e instanceof RuntimeException)throw (RuntimeException) e;elsethrow new ServletException(e.getMessage(), e);}} else {internalDoFilter(request,response);}} 4)在Filter.doFilter()方法中, 可以通過顯示地調用 FileterChain.doFilter()方法來調用另一個過濾器。
對以上代碼的分析(Analysis): A1)正如你所看到的,在doFilter()方法的最后一行會調用FilterChain.doFilter()方法; A2)如果某個過濾器時過濾器鏈中的最后一個過濾器,則會調用被請求的 servlet類的 service()方法。如果過濾器沒有調用chain.doFilter()方法,則不會調用后面的過濾器;
【9】應用程序 0)servlet文件目錄
1)程序源代碼 public final class Bootstrap {public static void main(String[] args) {//invoke: http://localhost:8080/Modern or http://localhost:8080/PrimitiveSystem.setProperty("catalina.base", System.getProperty("user.dir"));Connector connector = new HttpConnector();Wrapper wrapper1 = new StandardWrapper();wrapper1.setName("Primitive");wrapper1.setServletClass("servlet.PrimitiveServlet"); // attention for servlet class,要與你的servlet目錄相對應;Wrapper wrapper2 = new StandardWrapper();wrapper2.setName("Modern");wrapper2.setServletClass("servlet.ModernServlet"); // attention for servlet class,要與你的servlet目錄相對應;Context context = new StandardContext();// StandardContext's start method adds a default mappercontext.setPath("/myApp");context.setDocBase("myApp");LifecycleListener listener = new SimpleContextConfig();((Lifecycle) context).addLifecycleListener(listener);context.addChild(wrapper1);context.addChild(wrapper2);// for simplicity, we don't add a valve, but you can add// valves to context or wrapper just as you did in Chapter 6Loader loader = new WebappLoader();context.setLoader(loader);// context.addServletMapping(pattern, name);context.addServletMapping("/Primitive", "Primitive");context.addServletMapping("/Modern", "Modern");// add ContextConfig. This listener is important because it configures// StandardContext (sets configured to true), otherwise StandardContext// won't startconnector.setContainer(context);try {connector.initialize();((Lifecycle) connector).start();((Lifecycle) context).start();// make the application wait until we press a key.System.in.read();((Lifecycle) context).stop();}catch (Exception e) {e.printStackTrace();}} } 2)打印結果 E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-common. jar;lib/commons-collections.jar;lib/naming-resources.jar;lib/;lib/catalina.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\webroot com.tomca t.chapter11.startup.Bootstrap HttpConnector Opening server socket on all host IP addresses HttpConnector[8080] Starting background thread WebappLoader[/myApp]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\_\myApp StandardManager[/myApp]: Seeding random number generator class java.security.SecureRandom StandardManager[/myApp]: Seeding of random number generator has been completed StandardManager[/myApp]: IOException while loading persisted sessions: java.io.EOFException // // 這是從文件中加載 session對象到內存,由于沒有相關文件,所以加載失敗,拋出異常,但這不會影響我們訪問servlet,大家不要驚慌; java.io.EOFExceptionat java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.<init>(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at com.tomcat.chapter11.startup.Bootstrap.main(Bootstrap.java:55) StandardManager[/myApp]: Exception loading sessions from persistent storage java.io.EOFExceptionat java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.<init>(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at com.tomcat.chapter11.startup.Bootstrap.main(Bootstrap.java:55) ModernServlet -- init
【1】方法調用序列 1)對于每個引入的http 請求,連接器都會調用與其關聯的servlet容器的 invoke() 方法。然后,servlet容器會調用其所有子容器的invoke() 方法; 2)下圖展示了連接器接收到http 請求后的方法調用的協作圖;
3)上圖的具體steps 如下: step1)連接器創建 request 和 response對象; step2)連接器調用StandardContext.invoke()方法; step3)StandardContext.invoke()方法調用其管道的invoke() 方法。StandardContext的管道對象的基礎閥是 StandardCoantextValve類的實例,因此, StandardContext 的管道會調用 StandardContextValve.invoke()方法; step4)StandardContextValve.invoke()方法 獲取相應的Wrapper 實例處理 http請求,調用Wrapper實例的invoke()方法; step5)StandardWrapper類是Wrapper接口的標準實現,StandardWrapper.invoke()方法 會調用其管道對象的invoke()方法; step6)StandardWrapper的管道對象中的基礎閥是 StandardWrapperValve 類的實例,因此,會調用StandardWrapperValve.invoke()方法,StandardWrapperValve.invoke()方法會調用Wrapper實例的 allocate() 方法獲取servlet實例; step7)allocate()方法調用load() 方法載入相應的servlet類,若已經載入,則無需重復載入; step8)load()方法調用servlet實例的init()方法; step9)StandardWrapperValve調用servlet.service()方法; // Call the filter chain for this request// NOTE: This also calls the servlet's service() methodtry { // org.apache.catalina.core.StandardWrapperValve.invoke()String jspFile = wrapper.getJspFile();if (jspFile != null)sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);elsesreq.removeAttribute(Globals.JSP_FILE_ATTR);if ((servlet != null) && (filterChain != null)) {filterChain.doFilter(sreq, sres); // highlight line. doFilter() calls servlet.service()}sreq.removeAttribute(Globals.JSP_FILE_ATTR);} Attention)StandardContext類的構造函數會設置StandardContextValve類的一個實例作為其基礎閥;? public StandardContext() { // org.apache.catalina.core.StardardContextsuper();pipeline.setBasic(new StandardContextValve());namingResources.setContainer(this); } Attention)StandardWrapper類的構造函數也會設置一個 StandardWrapperValve實例作為其基礎閥: public StandardWrapper() { // org.apache.catalina.core.StardardWrappersuper();swValve=new StandardWrapperValve();pipeline.setBasic(swValve); } 4)依據上述(3)小節中的 “處理http 請求的方法調用協作圖”,本文按照慣例給出了具體的調用過程,如下:
4.1)本文第一張是借用了 “tomcat(10)安全性中章節【6.4】中Supplement-補充模塊”的第2張圖;(for spec info,please visit tomcat(10)安全性),這旨在說明從HttpConnector -> StandardContext.invoke() -> StandardPipeline.invoke()的調用過程;
4.2)本文接著上面的調用過程繼續分析,調用過程如下圖;旨在說明? StandardPipeline.invoke() -> StandardContextValve.invoke() -> StandardWrapper.invoke() -> StandardPipeline.invoke() -> StandardWrapperValve.invoke() -> ApplicationFilterChain().doFilter()? ->?ApplicationFilterChain().internalDoFilter() -> HttpServlet(ModernServlet).service() -> ModernServlet->doGet() 的調用過程.(Bingo)
對上述協作圖和詳細調用過程圖的分析(Analysis): A0)要知道Tomcat中有4種容器:Engine,Host,Context 和 Wrapper;(干貨——本文一直強調這一點,理解容器的層次結構對于理解tomcat非常重要) A1)StandardContext 和 StandardWrapper 都是容器:他們都繼承自 ContainerBase,只不過StandardWrapper是StandardContext的子容器,而StandardWrapper是最小的容器,即它沒有子容器; A2)下面分別看StandardWrapper,StandardContext的構造函數?和 ContainerBase 的變量定義; public final class StandardWrapper extends ContainerBase implements ServletConfig, Wrapper {public StandardWrapper() {super();swValve=new StandardWrapperValve();pipeline.setBasic(swValve);} } public class StandardContext extends ContainerBase implements Context {public StandardContext() { super();pipeline.setBasic(new StandardContextValve());namingResources.setContainer(this);} } public abstract class ContainerBase implements Container, Lifecycle, Pipeline {protected Pipeline pipeline = new StandardPipeline(this); // highlight line.protected HashMap children = new HashMap();protected int debug = 0; protected LifecycleSupport lifecycle = new LifecycleSupport(this); protected ArrayList listeners = new ArrayList(); protected Loader loader = null; protected Logger logger = null; protected Manager manager = null; protected Cluster cluster = null; protected Mapper mapper = null; protected HashMap mappers = new HashMap(); protected String mapperClass = null; protected String name = null; protected Container parent = null; protected ClassLoader parentClassLoader = null; protected Pipeline pipeline = new StandardPipeline(this); protected Realm realm = null; protected DirContext resources = null; protected static StringManager sm = StringManager.getManager(Constants.Package); protected boolean started = false; protected PropertyChangeSupport support = new PropertyChangeSupport(this); } A3)可以看到 父容器ContainerBase定義了管道StandardPipeline,而子容器StandardContext 設置StandardContextValve為基礎閥;而最小的容器StandardWrapper設置StandardWrapperValve為基礎閥; A4)也即 StandardContext 和?StandardWrapper 共用同一個管道,分別設置不同的基礎閥;(當然,可以分別設置非基礎閥,非基礎閥在基礎閥被調用之前調用);
【2】SingleThreadModel(已經被棄用了) 1)intro:servlet類可以實現 javax.servlet.SingleThreadModel 接口,這樣的servlet類也稱為 SingleThreadModel(STM)servlet類。根據servlet規范,實現此接口的目的是保證 servlet實例一次只處理一個請求; Attention)若 servlet類實現 SingleThreadModel接口,則可以保證絕不會有兩個線程同時執行該servlet.service()方法。這一點由 servlet容器通過控制對單一 servlet實例的同步訪問實現,或者維護一個 servlet實例池,然后將每個新請求分派給一個空閑的servlet實例。該接口并不能防止servlet訪問共享資源造成的同步問題,例如訪問類的靜態變量或訪問servlet作用域之外的類; (干貨——有很多程序員哥哥沒有讀懂這段話,想當然的認為,實現了該接口的servlet就是線程安全的。這種想法是錯誤的,請再度一遍上面的引文內容(原文作者說的,哈哈))
2)事實上,實現了 SingleThreadModel 接口的servlet類只能保證在同一時刻,只有一個線程在執行該 servlet實例的service()方法。但,為了提高執行 性能,servlet容器會創建多個STM servlet實例。也就是說,STM servlet.service()方法 會在多個STM?servlet實例中并發執行。如果servlet實例需要靜態類變量或類外的某些資源的話,就有可能引起同步問題; Atttention)在servlet 2.4中,SingleThreadModel接口已經被棄用了,因為它會使 servlet程序員誤以為該接口的servlet類就是多線程安全的;
【3】StandardWrapper 1)intro to?StandardWrapper:其主要任務是 載入它所代表的servlet類,并進行實例化; 2)StandardWrapper并不調用servlet的service方法,該任務由 StandardWrapperValve對象(StandardWrapper實例的管道對象中的基礎閥)完成; 3)StandardWrapperValve對象通過調用allocate()方法從 StandardWrapper實例中獲取servlet實例,在獲得servlet實例后,StandardWrapperValve實例就會調用servlet實例的service()方法; 【3.1】分配servlet實例? 1)分配servlet實例是由 StandardWrapper.allocate()方法來完成的(allocate方法返回請求的servelt實例); 2)allocate()方法分為兩部分(parts): p1)第一部分:?allocate()首先檢查 instance是否為null,若是, 則allocate()方法調用 loadServlet()方法載入相關的servlet類,然后 整型變量countAllocated加1,返回instance的值; p2)第二部分: p2.1)若StandardWrapper表示的servlet是一個STM servlet類,則allocate()會試圖從對象池中返回一個servlet實例。變量 instancePool 是一個 java.util.Stack類型的棧,其中保存了所有的STM servlet實例: private Stack instancePool = null; p2.2)只要STM servlet實例數不超過指定的最大值,allocate()方法會返回一個 STM servlet實例。整型變量maxInstances 保存了在棧中存儲的 STM servlet實例的最大值,default value = 20; private int maxInstances = 20; p2.3)而 nInstances 保存了當前 STM servlet實例的數量(初始為0);
3)源碼如下
public Servlet allocate() throws ServletException { //org.apache.catalina.core.StandardWrapper.allocate() // part 1 begins.if (debug >= 1) log("Allocating an instance");// If we are currently unloading this servlet, throw an exceptionif (unloading)throw new ServletException(sm.getString("standardWrapper.unloading", getName()));// If not SingleThreadedModel, return the same instance every timeif (!singleThreadModel) {// Load and initialize our instance if necessaryif (instance == null) {synchronized (this) {if (instance == null) {try {instance = loadServlet();} catch (ServletException e) {throw e;} catch (Throwable e) {throw new ServletException(sm.getString("standardWrapper.allocate"), e);}}}}if (!singleThreadModel) {if (debug >= 2)log(" Returning non-STM instance");countAllocated++;return (instance);}} // part1 ends. // part2 starts.synchronized (instancePool) {while (countAllocated >= nInstances) {// Allocate a new instance if possible, or else waitif (nInstances < maxInstances) {try {instancePool.push(loadServlet());nInstances++;} catch (ServletException e) {throw e;} catch (Throwable e) {throw new ServletException(sm.getString("standardWrapper.allocate"), e);}} else {try {instancePool.wait();} catch (InterruptedException e) {;}}}if (debug >= 2)log(" Returning allocated STM instance");countAllocated++;return (Servlet) instancePool.pop();}}// part2 ends. 【3.2】載入servlet類 1)StandardWrapper類實現了Wrapper接口的 load() 方法,load() 方法調用loadServlet()方法載入某個servlet類,并調用其 init() 方法,此時要傳入一個 javax.servlet.ServletConfig實例作為參數; 2)loadServlet() 方法是如何工作的 public synchronized void load() throws ServletException { // org.apache.catalina.core.StandardWrapper.load()instance = loadServlet(); }public synchronized Servlet loadServlet() throws ServletException { // org.apache.catalina.core.StandardWrapper.loadServlet()// Nothing to do if we already have an instance or an instance poolif (!singleThreadModel && (instance != null))return instance;PrintStream out = System.out;if (swallowOutput) {SystemLogHandler.startCapture();}Servlet servlet = null;try {// If this "servlet" is really a JSP file, get the right class.// HOLD YOUR NOSE - this is a kludge that avoids having to do special// case Catalina-specific code in Jasper - it also requires that the// servlet path be replaced by the <jsp-file> element content in// order to be completely effectiveString actualClass = servletClass;if ((actualClass == null) && (jspFile != null)) {Wrapper jspWrapper = (Wrapper)((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME);if (jspWrapper != null)actualClass = jspWrapper.getServletClass();}// Complain if no servlet class has been specifiedif (actualClass == null) {unavailable(null);throw new ServletException(sm.getString("standardWrapper.notClass", getName()));} // Acquire an instance of the class loader to be usedLoader loader = getLoader();if (loader == null) {unavailable(null);throw new ServletException(sm.getString("standardWrapper.missingLoader", getName()));} ClassLoader classLoader = loader.getClassLoader(); // Special case class loader for a container provided servletif (isContainerProvidedServlet(actualClass)) {classLoader = this.getClass().getClassLoader();log(sm.getString("standardWrapper.containerServlet", getName()));} // Load the specified servlet class from the appropriate class loaderClass classClass = null;try {if (classLoader != null) {classClass = classLoader.loadClass(actualClass);} else {classClass = Class.forName(actualClass);}} catch (ClassNotFoundException e) {unavailable(null);throw new ServletException(sm.getString("standardWrapper.missingClass", actualClass),e);}if (classClass == null) {unavailable(null);throw new ServletException(sm.getString("standardWrapper.missingClass", actualClass));} // Instantiate and initialize an instance of the servlet class itselftry {servlet = (Servlet) classClass.newInstance();} catch (ClassCastException e) {unavailable(null);// Restore the context ClassLoaderthrow new ServletException(sm.getString("standardWrapper.notServlet", actualClass), e);} catch (Throwable e) {unavailable(null);// Restore the context ClassLoaderthrow new ServletException(sm.getString("standardWrapper.instantiate", actualClass), e);} // Check if loading the servlet in this web application should be// allowedif (!isServletAllowed(servlet)) {throw new SecurityException(sm.getString("standardWrapper.privilegedServlet",actualClass));} // Special handling for ContainerServlet instancesif ((servlet instanceof ContainerServlet) &&isContainerProvidedServlet(actualClass)) {((ContainerServlet) servlet).setWrapper(this);} // Call the initialization method of this servlettry {instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet);servlet.init(facade);// Invoke jspInit on JSP pagesif ((loadOnStartup >= 0) && (jspFile != null)) {// Invoking jspInitHttpRequestBase req = new HttpRequestBase();HttpResponseBase res = new HttpResponseBase();req.setServletPath(jspFile);req.setQueryString("jsp_precompile=true");servlet.service(req, res);}instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet);} catch (UnavailableException f) {instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet, f);unavailable(f);throw f;} catch (ServletException f) {instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet, f);// If the servlet wanted to be unavailable it would have// said so, so do not call unavailable(null).throw f;} catch (Throwable f) {instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet, f);// If the servlet wanted to be unavailable it would have// said so, so do not call unavailable(null).throw new ServletException(sm.getString("standardWrapper.initException", getName()), f);} // Register our newly initialized instancesingleThreadModel = servlet instanceof SingleThreadModel;if (singleThreadModel) {if (instancePool == null)instancePool = new Stack();}fireContainerEvent("load", this);} finally {if (swallowOutput) {String log = SystemLogHandler.stopCapture();if (log != null && log.length() > 0) {if (getServletContext() != null) {getServletContext().log(log);} else {out.println(log);}}}}return servlet;}
step1)檢查當前的StandardWrapper類是否表示的是一個 STM servlet類,若不是,且變量instance不為null(表示以前已經載入過這個servlet),它就直接返回該實例; // Nothing to do if we already have an instance or an instance poolif (!singleThreadModel && (instance != null))return instance; step2)獲得 System.out 和 System.err 的輸出,便于它使用 javax.servlet.ServletConfig.log() 方法記錄日志消息: PrintStream out = System.out;if (swallowOutput) {SystemLogHandler.startCapture();} step3)定義類型為javax.servlet.Servlet 名為servlet 的變量,其表示已載入的servlet實例,會由 loadServlet()方法返回; Servlet servlet = null; step4)由于Catalina是一個JSP容器,故loadServlet()方法必須檢查請求的servlet是不是一個jsp 頁面。若是,則loadServlet() 方法需要獲取代表該jsp 頁面的實際servlet類; String actualClass = servletClass;if ((actualClass == null) && (jspFile != null)) {Wrapper jspWrapper = (Wrapper)((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME);if (jspWrapper != null)actualClass = jspWrapper.getServletClass(); } // public static final String JSP_SERVLET_NAME = "jsp";<span style="font-family: SimSun; line-height: 1.5; background-color: inherit;">? ? ? ? ? ?</span> step5)如果找不到該jsp 頁面的servlet類,則會使用變量 servletClass(actualClass)的值。若沒有調用StandardWrapper.serServletClass() 方法設置servletClass的值,則會拋出異常,并停止執行后續方法; // Complain if no servlet class has been specifiedif (actualClass == null) {unavailable(null);throw new ServletException(sm.getString("standardWrapper.notClass", getName()));}<span style="font-family: SimSun; background-color: rgb(255, 255, 255);">? ? ? ? ? ?</span> step6)這時,要載入的servlet類名已經解析完了,loadServlet()方法會獲取載入器 Loader loader = getLoader();public Loader getLoader() { // org.apache.catalina.core.ContainerBase.getLoader();if (loader != null)return (loader);if (parent != null)return (parent.getLoader());return (null);} step7)若找到載入器(loader),則loadServlet()方法調用getClassLoader()方法獲取一個ClassLoader; ClassLoader classLoader = loader.getClassLoader(); step8)Catalina提供了一些用于訪問servlet容器內部數據的專用servlet類。如果某個servlet類是這種專用的servlet,即若isContainerProvidedServlet()方法返回true,則變量 classLoader被賦值為另一種ClassLoader實例,如此一來,這個servlet實例就可以訪問Catalina的內部數據了; // Special case class loader for a container provided servletif (isContainerProvidedServlet(actualClass)) {classLoader = this.getClass().getClassLoader();log(sm.getString("standardWrapper.containerServlet", getName()));} step9)準備好類載入器和準備載入的servlet類名后,loadServlet()方法就可以載入servlet類了; // Load the specified servlet class from the appropriate class loaderClass classClass = null;try {if (classLoader != null) {classClass = classLoader.loadClass(actualClass);} else {classClass = Class.forName(actualClass);}} catch (ClassNotFoundException e) {unavailable(null);throw new ServletException(sm.getString("standardWrapper.missingClass", actualClass),e);} if (classClass == null) {unavailable(null);throw new ServletException(sm.getString("standardWrapper.missingClass", actualClass));} step10)實例化該servlet ? ? ? // Instantiate and initialize an instance of the servlet class itselftry {servlet = (Servlet) classClass.newInstance();} catch (ClassCastException e) {unavailable(null);// Restore the context ClassLoaderthrow new ServletException(sm.getString("standardWrapper.notServlet", actualClass), e);} catch (Throwable e) {unavailable(null);// Restore the context ClassLoaderthrow new ServletException(sm.getString("standardWrapper.instantiate", actualClass), e);}
step11)在loadServlet()方法實例化這個servlet之前,它會調用 isServletAllowed()方法檢查該servlet 類是否允許載入:? ? ? // Check if loading the servlet in this web application should be// allowedif (!isServletAllowed(servlet)) {throw new SecurityException(sm.getString("standardWrapper.privilegedServlet",actualClass));}
step12)若通過了安全檢查,它還會繼續檢查該servlet類是否是一個 ContainerServlet類型的servlet(實現了 org.apache.catalina.ContainerServlet接口的 servlet可以訪問Catalina的內部功能)。若該servlet類是一個 ContainerServlet,loadServlet()方法會調用 ContainerServlet.setWrapper(),傳入StandardWrapper實例;? // Special handling for ContainerServlet instancesif ((servlet instanceof ContainerServlet) &&isContainerProvidedServlet(actualClass)) {((ContainerServlet) servlet).setWrapper(this);} step13)觸發BEFORE_INIT_EVENT事件,調用servlet實例的 init()方法(init()方法傳入了javax.servlet.ServletConfig外觀對象):? // Call the initialization method of this servlettry {instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet);servlet.init(facade); // highlight line. step14)若變量 loadOnStartup 大于0, 且被請求的servlet類實際上是一個jsp 頁面,則servlet實例的service()方法;? if ((loadOnStartup >= 0) && (jspFile != null)) {// Invoking jspInitHttpRequestBase req = new HttpRequestBase();HttpResponseBase res = new HttpResponseBase();req.setServletPath(jspFile);req.setQueryString("jsp_precompile=true");servlet.service(req, res); // highlight line.}instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet); step15)觸發AFTER_INIT_EVENT事件 instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, step16)若StandardWrapper對象表示的servlet類是一個STM servlet,則將該servlet實例添加到servlet實例池中。因此會判斷 instancePool 是否為null,若是,則要給他賦值一個Stack 對象;? // Register our newly initialized instancesingleThreadModel = servlet instanceof SingleThreadModel;if (singleThreadModel) {if (instancePool == null)instancePool = new Stack(); // highlight line. } step17)在finally代碼塊中,loadServlet()方法停止捕獲System.out 和 System.err 對象,記錄在載入 ServletContext.log()方法的過程中產生的日志消息; finally {if (swallowOutput) {String log = SystemLogHandler.stopCapture();if (log != null && log.length() > 0) {if (getServletContext() != null) {getServletContext().log(log); // highlight line.} else {out.println(log);}}}} public ServletContext getServletContext() { org.apache.catalina.core.StandardWrapper.getServletContext()if (parent == null)return (null);else if (!(parent instanceof Context))return (null);elsereturn (((Context) parent).getServletContext());} step18)最后返回已載入的servlet實例; return servlet; 【3.3】ServletConfig對象 1)intro:在上述step13)中提到了 servlet.init(facade),而facade 是 javax.servlet.ServletConfig對象的一個外觀變量; 2)StandardWrapper對象是如何獲取 servletConfig 對象的?答案就在 StandardWrapper中,該類不僅實現了 Wrapper接口,還實現了 javax.servlet.ServletConfig 接口; public final class StandardWrapper extends ContainerBase implements ServletConfig, Wrapper { // org.apache.catalina.core.StandardWrapper // ...... } public interface ServletConfig { // javax.servlet.ServletConfig public String getServletName();public ServletContext getServletContext(); public String getInitParameter(String name);public Enumeration getInitParameterNames(); } 3)javax.servlet.ServletConfig 接口有4個方法:getServletContext() , getServletName(), ?getInitParameter(), ?getInitParameterNames()方法;下面對這4個方法進行說明; method1)getServletConfig()方法: public ServletContext getServletContext() { // org.apache.catalina.core.StandardWrapper.getServletContext()if (parent == null)return (null);else if (!(parent instanceof Context))return (null);elsereturn (((Context) parent).getServletContext());}/*** Return the servlet context for which this Context is a facade.*/public ServletContext getServletContext() { // org.apache.catalina.core.StandardContext.getServletContext()if (context == null)context = new ApplicationContext(getBasePath(), this);return (context);} Attention)正如以上代碼所展示的那樣,無法單獨使用一個Wrapper實例來表示一個 servlet 類的定義。Wrapper 實例必須駐留在某個 Context 容器中,這樣,當調用其父容器的getServletConfig()方法時,才能返回ServletContext類的一個實例; method2)getServletName()方法:該方法返回 servlet類的名字,該方法的簽名如下: ?public String getServletName() { // org.apache.catalina.core.StandardWrapper.getServletName()return (getName());} public String getName() { // org.apache.catalina.core.ContainerBase.getName(). // 因為 public final class StandardWrapper extends ContainerBasereturn (name);} method3)getInitParameter()方法:該方法返回指定初始參數的值 public String getInitParameter(String name) { // org.apache.catalina.core.StandardWrapper.getInitParameter()return (findInitParameter(name));} public String findInitParameter(String name) { // org.apache.catalina.core.StandardWrapper.findInitParameter()synchronized (parameters) {return ((String) parameters.get(name));}} 對getInitParameter()方法的分析(Analysis): A1)在StandardWrapper類中,初始化參數 parameters 存儲在一個 HashMap類型中; private HashMap parameters = new HashMap(); A2)通過addInitParameter()方法,傳入參數的名字 和 對應的值 來填充變量 parameters 的值: public void addInitParameter(String name, String value) { // org.apache.catalina.core.StandardWrapper.addInitParameter().synchronized (parameters) {parameters.put(name, value);}fireContainerEvent("addInitParameter", name); // highlight line.} public void fireContainerEvent(String type, Object data) {// org.apache.catalina.core.ContainerBase.fireContainerEvent().if (listeners.size() < 1)return;ContainerEvent event = new ContainerEvent(this, type, data);ContainerListener list[] = new ContainerListener[0];synchronized (listeners) {list = (ContainerListener[]) listeners.toArray(list);}for (int i = 0; i < list.length; i++)((ContainerListener) list[i]).containerEvent(event);} A3)StandardWrapper.getInitParameter()方法的實現如下:? public String getInitParameter(String name) {return (findInitParameter(name));} A4)findInitParameter()方法接收一個指定的初始化參數名的字符串變量,調用HashMap 變量 parameters的get()方法獲取初始化參數的值; public String findInitParameter(String name) { // org.apache.catalina.core.StandardWrapper.findInitParameter()synchronized (parameters) {return ((String) parameters.get(name)); // highlight line.}} method4)getInitParameterNames()方法: 該方法返回所有初始化參數的名字的集合,實際上是 java.util.Enumeration的實例;? public Enumeration getInitParameterNames() {synchronized (parameters) {return (new Enumerator(parameters.keySet()));}} 【3.4】servlet容器的父子關系 1)intro to StandardWrapper:Wrapper實例代表一個servlet實例,是最低級的容器,故Wrapper不能再有子容器,不應該調用addChild()方法添加子容器,否則拋出 java.lang.IllegalStateException 異常;(干貨review——Wrapper實例代表一個servlet實例,是最低級的容器,故Wrapper不能再有子容器) 2)org.apache.catalina.core.StandardWrapper.addChild()方法實現如下: public void addChild(Container child) {throw new IllegalStateException (sm.getString("standardWrapper.notChild"));} Attention)Wrapper容器的父容器只能是 Context 容器;若我們在設置父容器的時候,傳入了非Context容器,則拋出 java.lang.IllegalArgumentException 異常; public void setParent(Container container) { // org.apache.catalina.core.StandardWrapper.setParent().if ((container != null) && !(container instanceof Context)) throw new IllegalArgumentException(sm.getString("standardWrapper.notContext"));if (container instanceof StandardContext) {swallowOutput = ((StandardContext)container).getSwallowOutput();}super.setParent(container); // highlight line.} public void setParent(Container container) { // org.apache.catalina.core.ContainerBase.setParent().Container oldParent = this.parent;this.parent = container;support.firePropertyChange("parent", oldParent, this.parent);}
【4】 StandardWrapperFacade類?(干貨——應用了設計模式中的外觀模式) 1)problem+solution: 1.1)problem:StandardWrapper實例會調用它所載入的servlet類的實例的init()方法。init()方法需要一個javax.servlet.ServletConfig 實例,而StandardWrapper了本身也實現了 javax.servlet.ServletConfig 接口,所以,理論上 StandardWrapper需要將其中大部分公共方法對servlet程序員隱藏起來; 1.2)solution:為了實現這個目的,StandardWrapper類將自身實例包裝成 StandardWrapperFacade類的一個實例;
2)StandardWrapper類創建StandardWrapperFacade對象,并將自身作為參數傳入StandardWrapperFacade的構造器; private StandardWrapperFacade facade = new StandardWrapperFacade(this); // defined in StandardWrapper.java 3)StandardWrapperFacade的構造函數; public StandardWrapperFacade(StandardWrapper config) {super();this.config = (ServletConfig) config; // private ServletConfig config = null;} 4)因此當創建StandardWrapper對象調用servlet實例的 init()方法時,它會傳入StandardWrapperFacade類的一個實例。這樣,在servlet實例內調用 ServletConfig.getServletName(),ServletConfig.getInitParameter(), getInitParameterNames() ,getServletContext()方法會直接傳遞給 StandardWrapper類的相應方法; 5)org.apache.catalina.core.StandardWrapperFacade 的定義如下: public final class StandardWrapperFacade implements ServletConfig {public StandardWrapperFacade(StandardWrapper config) {super();this.config = (ServletConfig) config;}public String getServletName() {return config.getServletName();}public ServletContext getServletContext() {ServletContext theContext = config.getServletContext();if ((theContext != null) &&(theContext instanceof ApplicationContext))theContext = ((ApplicationContext) theContext).getFacade();return (theContext);}public String getInitParameter(String name) {return config.getInitParameter(name);}public Enumeration getInitParameterNames() {return config.getInitParameterNames();} }
【5】StandardWrapperValve類 1)StandardWrapperValve類是 StandardWrapper實例中的基礎閥,要完成兩個操作(Operations): public StandardWrapper() { // StandardWrapper的構造函數;super();swValve=new StandardWrapperValve();pipeline.setBasic(swValve);} O1)執行與該servlet實例關聯的全部過濾器;(干貨——這里引入了過濾器) O2)調用servlet實例的service()方法; 2)完成上述任務后,在 StandardWrapperValve.invoke()方法實現中會執行以下操作(Operations): O1)調用StandardWrapper.allocate()方法獲取該StandardWrapper實例所表示的 servlet實例; public void invoke(Request request, Response response,ValveContext valveContext)throws IOException, ServletException {long t1=System.currentTimeMillis();requestCount++;// Initialize local variables we may needboolean unavailable = false;Throwable throwable = null;StandardWrapper wrapper = (StandardWrapper) getContainer();ServletRequest sreq = request.getRequest();ServletResponse sres = response.getResponse();Servlet servlet = null;HttpServletRequest hreq = null;if (sreq instanceof HttpServletRequest)hreq = (HttpServletRequest) sreq;HttpServletResponse hres = null;if (sres instanceof HttpServletResponse)hres = (HttpServletResponse) sres;// Check for the application being marked unavailableif (!((Context) wrapper.getParent()).getAvailable()) {hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,sm.getString("standardContext.isUnavailable"));unavailable = true;}// Check for the servlet being marked unavailableif (!unavailable && wrapper.isUnavailable()) {log(sm.getString("standardWrapper.isUnavailable",wrapper.getName()));if (hres == null) {; // NOTE - Not much we can do generically} else {long available = wrapper.getAvailable();if ((available > 0L) && (available < Long.MAX_VALUE))hres.setDateHeader("Retry-After", available);hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,sm.getString("standardWrapper.isUnavailable",wrapper.getName()));}unavailable = true;}// Allocate a servlet instance to process this requesttry {if (!unavailable) {servlet = wrapper.allocate(); // highlight line.} // ...... O2)調用私有方法 createFilterChain(),創建過濾器鏈; // Create the filter chain for this request ApplicationFilterChain filterChain = createFilterChain(request, servlet); // for create FilterChain方法,本章節末尾; private ApplicationFilterChain createFilterChain(Request request, Servlet servlet) { if (servlet == null)return (null);ApplicationFilterChain filterChain = new ApplicationFilterChain(); filterChain.setServlet(servlet);StandardWrapper wrapper = (StandardWrapper) getContainer();filterChain.setSupport(wrapper.getInstanceSupport());// Acquire the filter mappings for this ContextStandardContext context = (StandardContext) wrapper.getParent();FilterMap filterMaps[] = context.findFilterMaps();// If there are no filter mappings, we are doneif ((filterMaps == null) || (filterMaps.length == 0))return (filterChain);// Acquire the information we will need to match filter mappingsString requestPath = null;if (request instanceof HttpRequest) {HttpServletRequest hreq =(HttpServletRequest) request.getRequest();String contextPath = hreq.getContextPath();if (contextPath == null)contextPath = "";String requestURI = ((HttpRequest) request).getDecodedRequestURI();if (requestURI.length() >= contextPath.length())requestPath = requestURI.substring(contextPath.length());}String servletName = wrapper.getName();int n = 0;// Add the relevant path-mapped filters to this filter chainfor (int i = 0; i < filterMaps.length; i++) {if (!matchFiltersURL(filterMaps[i], requestPath))continue;ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig == null) {continue;}filterChain.addFilter(filterConfig);n++;}// Add filters that match on servlet name secondfor (int i = 0; i < filterMaps.length; i++) {if (!matchFiltersServlet(filterMaps[i], servletName))continue;ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig == null) {continue;}filterChain.addFilter(filterConfig);n++;}return (filterChain);} O3)調用過濾器鏈的 doFilter()方法,其中包括調用servlet實例的service()方法; try {String jspFile = wrapper.getJspFile();if (jspFile != null)sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);elsesreq.removeAttribute(Globals.JSP_FILE_ATTR);if ((servlet != null) && (filterChain != null)) {filterChain.doFilter(sreq, sres); // hightlight line.}sreq.removeAttribute(Globals.JSP_FILE_ATTR); // ......public void doFilter(ServletRequest request, ServletResponse response) //org.apache.catlina.core.ApplicationFilterChain.doFileter()throws IOException, ServletException {if( System.getSecurityManager() != null ) {final ServletRequest req = request;final ServletResponse res = response;try {java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction(){public Object run() throws ServletException, IOException {internalDoFilter(req,res); // highlight line. internalDoFilter() 參見文末.return null;}});} catch( PrivilegedActionException pe) {Exception e = pe.getException();if (e instanceof ServletException)throw (ServletException) e;else if (e instanceof IOException)throw (IOException) e;else if (e instanceof RuntimeException)throw (RuntimeException) e;elsethrow new ServletException(e.getMessage(), e);}} else {internalDoFilter(request,response);}} private void internalDoFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException { //org.apache.catalina.core.ApplicationFilterChain.internalDoFilter().// Construct an iterator the first time this method is calledif (this.iterator == null)this.iterator = filters.iterator();// Call the next filter if there is oneif (this.iterator.hasNext()) {ApplicationFilterConfig filterConfig =(ApplicationFilterConfig) iterator.next();Filter filter = null;try {filter = filterConfig.getFilter();support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,filter, request, response);filter.doFilter(request, response, this);support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,filter, request, response);} //......return;}// We fell off the end of the chain -- call the servlet instancetry {support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,servlet, request, response);if ((request instanceof HttpServletRequest) &&(response instanceof HttpServletResponse)) { servlet.service((HttpServletRequest) request, (HttpServletResponse) response); // 這不就是你夢寐以求的service()方法嗎?哈哈。} else {servlet.service(request, response); // and this highlight line.}support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,servlet, request, response);} //......} O4)釋放過濾器鏈; try {if (filterChain != null)filterChain.release(); // highlight line.} catch (Throwable e) {log(sm.getString("standardWrapper.releaseFilters",wrapper.getName()), e);if (throwable == null) {throwable = e;exception(request, response, e);}} void release() { //org.apache.catalina.core.ApplicationFilterChain.release()this.filters.clear();this.iterator = iterator;this.servlet = null;} O5)調用Wrapper實例的 deallocate()方法; // Deallocate the allocated servlet instancetry {if (servlet != null) {wrapper.deallocate(servlet); // highlight line.}} catch (Throwable e) {log(sm.getString("standardWrapper.deallocateException",wrapper.getName()), e);if (throwable == null) {throwable = e;exception(request, response, e);}}public void deallocate(Servlet servlet) throws ServletException { //org.apache.catalina.core.StandardWrapper.deallocate()// If not SingleThreadModel, no action is requiredif (!singleThreadModel) {countAllocated--;return;}synchronized (instancePool) {countAllocated--;instancePool.push(servlet);instancePool.notify();}} O6)若該servlet類再也不會被使用到,調用Wrapper實例的unload()方法; // If this servlet has been marked permanently unavailable,// unload it and release this instancetry {if ((servlet != null) &&(wrapper.getAvailable() == Long.MAX_VALUE)) {wrapper.unload(); // highlight line.}} // ......long t2=System.currentTimeMillis();long time=t2-t1;processingTime+=time;if( time > maxTime ) maxTime=time;} Attention)以上調用過程中,最重要的是對 createFilterChain()方法和過濾器鏈的 doFilter()方法的調用。createFilterChain()方法創建一個 ApplicationFilterChain實例,并將所有需要應用到該Wrapper實例所代表的servlet實例的過濾器添加到其中; private ApplicationFilterChain createFilterChain(Request request,Servlet servlet) {if (servlet == null)return (null);ApplicationFilterChain filterChain =new ApplicationFilterChain();filterChain.setServlet(servlet);StandardWrapper wrapper = (StandardWrapper) getContainer();filterChain.setSupport(wrapper.getInstanceSupport());StandardContext context = (StandardContext) wrapper.getParent();FilterMap filterMaps[] = context.findFilterMaps();if ((filterMaps == null) || (filterMaps.length == 0))return (filterChain);// Acquire the information we will need to match filter mappingsString requestPath = null;if (request instanceof HttpRequest) {HttpServletRequest hreq =(HttpServletRequest) request.getRequest();String contextPath = hreq.getContextPath();if (contextPath == null)contextPath = "";String requestURI = ((HttpRequest) request).getDecodedRequestURI();if (requestURI.length() >= contextPath.length())requestPath = requestURI.substring(contextPath.length());}String servletName = wrapper.getName();int n = 0; // Add the relevant path-mapped filters to this filter chainfor (int i = 0; i < filterMaps.length; i++) {if (!matchFiltersURL(filterMaps[i], requestPath)) continue;ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig == null) {continue; }filterChain.addFilter(filterConfig); n++;}// Add filters that match on servlet name secondfor (int i = 0; i < filterMaps.length; i++) {if (!matchFiltersServlet(filterMaps[i], servletName)) continue;ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig == null) {continue;}filterChain.addFilter(filterConfig);n++;}return (filterChain); } public synchronized void unload() throws ServletException {if (!singleThreadModel && (instance == null))return;unloading = true;if (countAllocated > 0) {int nRetries = 0;while (nRetries < 10) {if (nRetries == 0) {log("Waiting for " + countAllocated +" instance(s) to be deallocated");}try {Thread.sleep(50);} catch (InterruptedException e) {;}nRetries++;}}ClassLoader oldCtxClassLoader =Thread.currentThread().getContextClassLoader();ClassLoader classLoader = instance.getClass().getClassLoader();PrintStream out = System.out;if (swallowOutput) {SystemLogHandler.startCapture();}try {instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_DESTROY_EVENT, instance);Thread.currentThread().setContextClassLoader(classLoader);instance.destroy();instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_DESTROY_EVENT, instance);} catch (Throwable t) {instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_DESTROY_EVENT, instance, t);instance = null;instancePool = null;nInstances = 0;fireContainerEvent("unload", this);unloading = false;throw new ServletException(sm.getString("standardWrapper.destroyException", getName()),t);} finally {Thread.currentThread().setContextClassLoader(oldCtxClassLoader);if (swallowOutput) {String log = SystemLogHandler.stopCapture();if (log != null && log.length() > 0) {if (getServletContext() != null) {getServletContext().log(log);} else {out.println(log);}}}}instance = null;if (singleThreadModel && (instancePool != null)) {try {Thread.currentThread().setContextClassLoader(classLoader);while (!instancePool.isEmpty()) {((Servlet) instancePool.pop()).destroy();}} catch (Throwable t) {instancePool = null;nInstances = 0;unloading = false;fireContainerEvent("unload", this);throw new ServletException(sm.getString("standardWrapper.destroyException",getName()), t);} finally {Thread.currentThread().setContextClassLoader(oldCtxClassLoader);}instancePool = null;nInstances = 0;}singleThreadModel = false;unloading = false;fireContainerEvent("unload", this); }
【6】 FilterDef類(org.apache.catalina.deploy.FilterDef) 1)intro:FilterDef 是一個過濾器的定義; 2)FilterDef類中的每個屬性表示在定義filter元素時聲明的子元素。其中Map 類型的變量parameters 存儲了初始化過濾器時所需要的所有參數。addInitParameter()方法用于向parameters 中添加新的 name/value 形式的參數名和對應的值; 3)其定義源碼如下: public final class FilterDef { // org.apache.catalina.deploy.FilterDef private String description = null;public String getDescription() {return (this.description);}public void setDescription(String description) {this.description = description;} private String displayName = null;public String getDisplayName() {return (this.displayName);}public void setDisplayName(String displayName) {this.displayName = displayName;}private String filterClass = null;public String getFilterClass() {return (this.filterClass);}public void setFilterClass(String filterClass) {this.filterClass = filterClass;} private String filterName = null;public String getFilterName() {return (this.filterName);}public void setFilterName(String filterName) {this.filterName = filterName;} private String largeIcon = null;public String getLargeIcon() {return (this.largeIcon);}public void setLargeIcon(String largeIcon) {this.largeIcon = largeIcon;} private Map parameters = new HashMap();public Map getParameterMap() {return (this.parameters);}private String smallIcon = null;public String getSmallIcon() {return (this.smallIcon);}public void setSmallIcon(String smallIcon) {this.smallIcon = smallIcon;} public void addInitParameter(String name, String value) {parameters.put(name, value);}public String toString() {StringBuffer sb = new StringBuffer("FilterDef[");sb.append("filterName=");sb.append(this.filterName);sb.append(", filterClass=");sb.append(this.filterClass);sb.append("]");return (sb.toString());} }
【7】ApplicationFilterConfig類(org.apache.catalina.core.ApplicationFilterConfig-應用過濾器配置類) 1)intro:ApplicationFilterConfig類實現了 javax.servlet.FilterConfig接口,該類用于管理web 應用程序第1次啟動時創建的所有過濾器實例; 2)類簽名:final class ApplicationFilterConfig implements FilterConfig? 3)可以通過把一個 org.apache.catalina.Context對象和 一個 FilterDef對象傳遞給 ApplicationFilterConfig類的構造函數來創建一個 ApplicationFilterConfig對象:? public ApplicationFilterConfig(Context context, FilterDef filterDef)throws ClassCastException, ClassNotFoundException,IllegalAccessException, InstantiationException,ServletException {super();this.context = context;setFilterDef(filterDef);} 對以上代碼的分析(Analysis): A1)Context對象表示一個web 應用程序; A2)FilterDef對象表示一個過濾器的定義; 4)ApplicationFilterConfig.getFilter()方法:會返回一個 javax.servlet.Filter對象,該方法負責載入并實例化一個過濾器類;? public String getFilterName() { // org.apache.catalina.core.ApplicationFilterConfig.getFilterName().return (filterDef.getFilterName()); }
【8】ApplicationFilterChain類(org.apache.catalina.core.ApplicationFilterChain) 1)intro:?ApplicationFilterChain類實現了 javax.servlet.FilterChain接口,StandardWrapperValve.invoke() 方法會創建 ApplicationFilterChain類的一個實例,并調用其 doFilter()方法; 2)Filter接口的doFilter()方法的簽名如下: public interface Filter { // javax.servlet.Filterpublic void init(FilterConfig filterConfig) throws ServletException; public void doFilter ( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException; public void destroy(); }3)ApplicationFilterChain.doFilter()方法會將?ApplicationFilterChain 類自身作為第3個參數傳遞給過濾器的 doFilter()方法; public void doFilter(ServletRequest request, ServletResponse response) //org.apache.catalina.ApplicationFilterChain.doFileter().throws IOException, ServletException {if( System.getSecurityManager() != null ) {final ServletRequest req = request;final ServletResponse res = response;try {java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction(){public Object run() throws ServletException, IOException {internalDoFilter(req,res);return null;}});} catch( PrivilegedActionException pe) {Exception e = pe.getException();if (e instanceof ServletException)throw (ServletException) e;else if (e instanceof IOException)throw (IOException) e;else if (e instanceof RuntimeException)throw (RuntimeException) e;elsethrow new ServletException(e.getMessage(), e);}} else {internalDoFilter(request,response);}} 4)在Filter.doFilter()方法中, 可以通過顯示地調用 FileterChain.doFilter()方法來調用另一個過濾器。
對以上代碼的分析(Analysis): A1)正如你所看到的,在doFilter()方法的最后一行會調用FilterChain.doFilter()方法; A2)如果某個過濾器時過濾器鏈中的最后一個過濾器,則會調用被請求的 servlet類的 service()方法。如果過濾器沒有調用chain.doFilter()方法,則不會調用后面的過濾器;
【9】應用程序 0)servlet文件目錄
1)程序源代碼 public final class Bootstrap {public static void main(String[] args) {//invoke: http://localhost:8080/Modern or http://localhost:8080/PrimitiveSystem.setProperty("catalina.base", System.getProperty("user.dir"));Connector connector = new HttpConnector();Wrapper wrapper1 = new StandardWrapper();wrapper1.setName("Primitive");wrapper1.setServletClass("servlet.PrimitiveServlet"); // attention for servlet class,要與你的servlet目錄相對應;Wrapper wrapper2 = new StandardWrapper();wrapper2.setName("Modern");wrapper2.setServletClass("servlet.ModernServlet"); // attention for servlet class,要與你的servlet目錄相對應;Context context = new StandardContext();// StandardContext's start method adds a default mappercontext.setPath("/myApp");context.setDocBase("myApp");LifecycleListener listener = new SimpleContextConfig();((Lifecycle) context).addLifecycleListener(listener);context.addChild(wrapper1);context.addChild(wrapper2);// for simplicity, we don't add a valve, but you can add// valves to context or wrapper just as you did in Chapter 6Loader loader = new WebappLoader();context.setLoader(loader);// context.addServletMapping(pattern, name);context.addServletMapping("/Primitive", "Primitive");context.addServletMapping("/Modern", "Modern");// add ContextConfig. This listener is important because it configures// StandardContext (sets configured to true), otherwise StandardContext// won't startconnector.setContainer(context);try {connector.initialize();((Lifecycle) connector).start();((Lifecycle) context).start();// make the application wait until we press a key.System.in.read();((Lifecycle) context).stop();}catch (Exception e) {e.printStackTrace();}} } 2)打印結果 E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-common. jar;lib/commons-collections.jar;lib/naming-resources.jar;lib/;lib/catalina.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\webroot com.tomca t.chapter11.startup.Bootstrap HttpConnector Opening server socket on all host IP addresses HttpConnector[8080] Starting background thread WebappLoader[/myApp]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\_\myApp StandardManager[/myApp]: Seeding random number generator class java.security.SecureRandom StandardManager[/myApp]: Seeding of random number generator has been completed StandardManager[/myApp]: IOException while loading persisted sessions: java.io.EOFException // // 這是從文件中加載 session對象到內存,由于沒有相關文件,所以加載失敗,拋出異常,但這不會影響我們訪問servlet,大家不要驚慌; java.io.EOFExceptionat java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.<init>(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at com.tomcat.chapter11.startup.Bootstrap.main(Bootstrap.java:55) StandardManager[/myApp]: Exception loading sessions from persistent storage java.io.EOFExceptionat java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.<init>(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at com.tomcat.chapter11.startup.Bootstrap.main(Bootstrap.java:55) ModernServlet -- init
總結
以上是生活随笔為你收集整理的tomcat(11)org.apache.catalina.core.StandardWrapper源码剖析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 陶阳和郭德纲什么关系(陶阳为什么会认郭德
- 下一篇: 电脑本地连接不见了解决方法