tomcat(12)org.apache.catalina.core.StandardContext源码剖析
生活随笔
收集整理的這篇文章主要介紹了
tomcat(12)org.apache.catalina.core.StandardContext源码剖析
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
【0】README
0)本文部分文字描述轉(zhuǎn)自 “how tomcat works”,旨在學(xué)習(xí)?“tomcat(12)StandardContext源碼剖析”?的基礎(chǔ)知識(shí);
1)Context實(shí)例表示一個(gè)具體的web 應(yīng)用程序,其中包含一個(gè)或多個(gè)Wrapper實(shí)例,每個(gè)Wrapper 表示一個(gè)具體的servlet定義;
2)Context容器還需要其他組件的支持,如載入器和Session 管理器。本章要intro 的 StandardContext是 catalina中Context接口的標(biāo)準(zhǔn)實(shí)現(xiàn);
3)本文首先會(huì)回顧StandardContext類的實(shí)例化和配置,然后討論與其相關(guān)的StandardMapper類 和 ContextConfig類。接下來,學(xué)習(xí)對(duì)于引入的每個(gè)HTTP 請(qǐng)求的方法調(diào)用序列;
【1】StardardContext 配置 1)intro:創(chuàng)建了StandardContext實(shí)例后,必須調(diào)用其start() 方法來為引入的每個(gè)http 請(qǐng)求提供服務(wù); 1.1)StandardContext對(duì)象可能啟動(dòng)失敗:這時(shí)available 設(shè)置為false,該屬性表明StandardContext 對(duì)象是否可用; 1.2)若啟動(dòng)成功:available=true,則表明StandardContext 對(duì)象配置正確; 2)正確配置StandardContext后,StandardContext才能讀入并解析默認(rèn)的 web.xml文件,該文件位于 %CATALINA_HOME%/conf 目錄下,該文件的內(nèi)容會(huì)應(yīng)用到所有部署到tomcat 中的應(yīng)用程序中。這也保證了StandardContext 實(shí)例可以處理應(yīng)用程序級(jí)的web.xml 文件; 3)StandardContext.configured屬性:表明StandardContext 實(shí)例是否正確配置 3.1)StandardContext使用了一個(gè)事件監(jiān)聽器作為其配置器;(干貨——StandardContext使用了一個(gè)事件監(jiān)聽器作為其配置器,參見下圖中StandardContext.start()方法中l(wèi)ifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null)?調(diào)用) 3.2)當(dāng)調(diào)用StandardContext.start()方法時(shí),其中要做的一件事情是,觸發(fā)一個(gè)生命周期事件。該事件調(diào)用監(jiān)聽器,對(duì)StandardContext實(shí)例進(jìn)行配置; 3.3)若配置成功,則監(jiān)聽器將 configured設(shè)置為true,否則StandardContext 實(shí)例拒絕啟動(dòng),也就無法為http 請(qǐng)求提供服務(wù)了; Attention)下面的圖片借用了 “tomcat(10)安全性中章節(jié)【6.4】中Supplement-補(bǔ)充模塊”的第1張圖;
【1.1】StandardContext 類的構(gòu)造函數(shù) 1)源代碼如下: public StandardContext() { // org.apache.catalina.core.StandardContext.StandardContext().super();pipeline.setBasic(new StandardContextValve());namingResources.setContainer(this);} 2)構(gòu)造函數(shù)最重要的事情:是為?StandardContext 實(shí)例的管道對(duì)象設(shè)置基礎(chǔ)閥;
【1.2】啟動(dòng)StandardContext 實(shí)例 1)start()方法會(huì)初始化 StandardContext對(duì)象,用生命周期監(jiān)聽器配置?StandardContext實(shí)例; 1.1)StandardContext 對(duì)象可能啟動(dòng)失敗:這時(shí)available 設(shè)置為false,該屬性表明StandardContext 對(duì)象是否可用; 1.2)若啟動(dòng)成功:available=true,則表明StandardContext 對(duì)象配置正確,與其關(guān)聯(lián)的子容器和組件都正確啟動(dòng); 2)正確配置后,StandardContext實(shí)例可以準(zhǔn)備為引入的 http 請(qǐng)求提供服務(wù)了。若期間發(fā)生了錯(cuò)誤,則available設(shè)置為false; 3)StandardContext.configured屬性:表明StandardContext 實(shí)例是否正確配置 3.1)在start()方法的末尾,?StandardContext實(shí)例會(huì)檢查 configured變量的值,若configured設(shè)置為 true,則StandardContext 啟動(dòng)成功; 3.2)否則,調(diào)用stop() 方法,關(guān)閉在start() 方法已經(jīng)啟動(dòng)的所有組件; public synchronized void start() throws LifecycleException { //org.apache.catalina.core.StandardContext.start()方法if (started)throw new LifecycleException(sm.getString("containerBase.alreadyStarted", logName()));if (debug >= 1)log("Starting");// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);if (debug >= 1)log("Processing start(), current available=" + getAvailable());setAvailable(false);setConfigured(false);boolean ok = true;// Add missing components as necessaryif (webappResources == null) { // (1) Required by Loaderif (debug >= 1)log("Configuring default Resources");try {if ((docBase != null) && (docBase.endsWith(".war")))setResources(new WARDirContext());elsesetResources(new FileDirContext());} catch (IllegalArgumentException e) {log("Error initializing resources: " + e.getMessage());ok = false;}}if (ok) {if (!resourcesStart())ok = false;}// Install DefaultContext configurationif (!getOverride()) {Container host = getParent();if (host instanceof StandardHost) {((StandardHost)host).installDefaultContext(this);Container engine = host.getParent();if( engine instanceof StandardEngine ) {((StandardEngine)engine).installDefaultContext(this);}}}if (getLoader() == null) { // (2) Required by Managerif (getPrivileged()) {if (debug >= 1)log("Configuring privileged default Loader");setLoader(new WebappLoader(this.getClass().getClassLoader()));} else {if (debug >= 1)log("Configuring non-privileged default Loader");setLoader(new WebappLoader(getParentClassLoader()));}}if (getManager() == null) { // (3) After prerequisitesif (debug >= 1)log("Configuring default Manager");setManager(new StandardManager());}// Initialize character set mappergetCharsetMapper();// Post work directorypostWorkDirectory();// Reading the "catalina.useNaming" environment variableString useNamingProperty = System.getProperty("catalina.useNaming");if ((useNamingProperty != null)&& (useNamingProperty.equals("false"))) {useNaming = false;}if (ok && isUseNaming()) {if (namingContextListener == null) {namingContextListener = new NamingContextListener();namingContextListener.setDebug(getDebug());namingContextListener.setName(getNamingContextName());addLifecycleListener(namingContextListener);}}// Binding threadClassLoader oldCCL = bindThread();// Standard container startupif (debug >= 1)log("Processing standard container startup");if (ok) {try {addDefaultMapper(this.mapperClass);started = true;// Start our subordinate components, if anyif ((loader != null) && (loader instanceof Lifecycle))((Lifecycle) loader).start();if ((logger != null) && (logger instanceof Lifecycle))((Lifecycle) logger).start();// Unbinding threadunbindThread(oldCCL);// Binding threadoldCCL = bindThread();if ((cluster != null) && (cluster instanceof Lifecycle))((Lifecycle) cluster).start();if ((realm != null) && (realm instanceof Lifecycle))((Lifecycle) realm).start();if ((resources != null) && (resources instanceof Lifecycle))((Lifecycle) resources).start();// Start our Mappers, if anyMapper mappers[] = findMappers();for (int i = 0; i < mappers.length; i++) {if (mappers[i] instanceof Lifecycle)((Lifecycle) mappers[i]).start();}// Start our child containers, if anyContainer children[] = findChildren();for (int i = 0; i < children.length; i++) {if (children[i] instanceof Lifecycle)((Lifecycle) children[i]).start();}// Start the Valves in our pipeline (including the basic),// if anyif (pipeline instanceof Lifecycle)((Lifecycle) pipeline).start();// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(START_EVENT, null);if ((manager != null) && (manager instanceof Lifecycle))((Lifecycle) manager).start();} finally {// Unbinding threadunbindThread(oldCCL);}}if (!getConfigured())ok = false;// We put the resources into the servlet contextif (ok)getServletContext().setAttribute(Globals.RESOURCES_ATTR, getResources());// Binding threadoldCCL = bindThread();// Create context attributes that will be requiredif (ok) {if (debug >= 1)log("Posting standard context attributes");postWelcomeFiles();}// Configure and call application event listeners and filtersif (ok) {if (!listenerStart())ok = false;}if (ok) {if (!filterStart())ok = false;}// Unbinding threadunbindThread(oldCCL);// Set available status depending upon startup successif (ok) {if (debug >= 1)log("Starting completed");setAvailable(true);} else {log(sm.getString("standardContext.startFailed"));try {stop();} catch (Throwable t) {log(sm.getString("standardContext.startCleanup"), t);}setAvailable(false);throw new LifecycleException(sm.getString("standardContext.startFailed"));}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);// Load and initialize all "load on startup" servletsoldCCL = bindThread();loadOnStartup(findChildren());unbindThread(oldCCL);} 對(duì)以上代碼的調(diào)用過程進(jìn)行分析-start()方法需要完成以下工作(works): work1)觸發(fā) BEFORE_START 事件; public synchronized void start() throws LifecycleException { //org.apache.catalina.core.StandardContext.start()方法。if (started)throw new LifecycleException(sm.getString("containerBase.alreadyStarted", logName()));if (debug >= 1)log("Starting");// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); work2)將 availability 屬性設(shè)置為false; work3)將 configured 屬性設(shè)置為 false; if (debug >= 1)log("Processing start(), current available=" + getAvailable());setAvailable(false);setConfigured(false);boolean ok = true; work4)配置資源; // Add missing components as necessaryif (webappResources == null) { // (1) Required by Loaderif (debug >= 1)log("Configuring default Resources");try {if ((docBase != null) && (docBase.endsWith(".war")))setResources(new WARDirContext());elsesetResources(new FileDirContext());} catch (IllegalArgumentException e) {log("Error initializing resources: " + e.getMessage());ok = false;}}if (ok) {if (!resourcesStart())ok = false;}// Install DefaultContext configurationif (!getOverride()) {Container host = getParent();if (host instanceof StandardHost) {((StandardHost)host).installDefaultContext(this);Container engine = host.getParent();if( engine instanceof StandardEngine ) {((StandardEngine)engine).installDefaultContext(this);}}} work5)設(shè)置載入器; if (getLoader() == null) { // (2) Required by Managerif (getPrivileged()) {if (debug >= 1)log("Configuring privileged default Loader");setLoader(new WebappLoader(this.getClass().getClassLoader()));} else {if (debug >= 1)log("Configuring non-privileged default Loader");setLoader(new WebappLoader(getParentClassLoader()));}} work6)設(shè)置Session 管理器; if (getManager() == null) { // (3) After prerequisitesif (debug >= 1)log("Configuring default Manager");setManager(new StandardManager());} work7)初始化字符集映射器; // Initialize character set mappergetCharsetMapper(); // defined in start().public CharsetMapper getCharsetMapper() {// Create a mapper the first time it is requestedif (this.charsetMapper == null) {try {Class clazz = Class.forName(charsetMapperClass);this.charsetMapper =(CharsetMapper) clazz.newInstance();} catch (Throwable t) {this.charsetMapper = new CharsetMapper();}}return (this.charsetMapper);} work8)啟動(dòng)與該Context 容器相關(guān)聯(lián)的組件; // Post work directorypostWorkDirectory(); // Reading the "catalina.useNaming" environment variableString useNamingProperty = System.getProperty("catalina.useNaming");if ((useNamingProperty != null)&& (useNamingProperty.equals("false"))) {useNaming = false;}if (ok && isUseNaming()) {if (namingContextListener == null) {namingContextListener = new NamingContextListener();namingContextListener.setDebug(getDebug());namingContextListener.setName(getNamingContextName());addLifecycleListener(namingContextListener);}}// Binding threadClassLoader oldCCL = bindThread(); work9)啟動(dòng)子容器; work10)啟動(dòng)管道對(duì)象; work11)啟動(dòng)Session 管理器;
work12)觸發(fā) START 事件,在這里監(jiān)聽器(ContextConfig 實(shí)例)會(huì)執(zhí)行一些配置操作,若配置成功,ContextConfig 實(shí)例會(huì)將 StandardContext.configured 變量設(shè)置為 true; // Set available status depending upon startup successif (ok) {if (debug >= 1)log("Starting completed");setAvailable(true);} else {log(sm.getString("standardContext.startFailed"));try {stop();} catch (Throwable t) {log(sm.getString("standardContext.startCleanup"), t);}setAvailable(false);throw new LifecycleException(sm.getString("standardContext.startFailed"));} work13)檢查 configured 屬性的值,若為true,則調(diào)用 postWelcomePages()方法,載入那些需要在啟動(dòng)時(shí)就載入的子容器,即 Wrapper實(shí)例,將 availability屬性設(shè)置為 true。若 configured 變量為false, 則調(diào)用stop() 方法; // Create context attributes that will be requiredif (ok) { // defined in start() method.if (debug >= 1)log("Posting standard context attributes");postWelcomeFiles();}private void postWelcomeFiles() {getServletContext().setAttribute("org.apache.catalina.WELCOME_FILES",welcomeFiles);} work14)觸發(fā) AFTER_START 事件;? ? ? // Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); // highlight line.// Load and initialize all "load on startup" servletsoldCCL = bindThread();loadOnStartup(findChildren());unbindThread(oldCCL);
【1.3】 org.apahce.catalina.core.StandardContext.invoke() 方法 1)該方法首先會(huì)檢查應(yīng)用程序是否正在重載過程中,若是,則等待應(yīng)用程序重載完成。然后,它調(diào)用其父類的 ContainerBase.invoke() 方法; public void invoke(Request request, Response response) throws IOException, ServletException { // Wait if we are reloadingwhile (getPaused()) { // 返回 paused屬性的值,當(dāng)paused為true時(shí),表明應(yīng)用程序正在重載;try {Thread.sleep(1000);} catch (InterruptedException e) {;}}// Normal request processingif (swallowOutput) {try {SystemLogHandler.startCapture();super.invoke(request, response);} finally {String log = SystemLogHandler.stopCapture();if (log != null && log.length() > 0) {log(log);}}} else {super.invoke(request, response);}} Attention)上述的StandardContext.invoke() 方法是tomcat 4中的實(shí)現(xiàn),而在 tomcat5中,StandardContext 并沒有提供 invoke()方法的實(shí)現(xiàn),所以會(huì)執(zhí)行 ContainerBase.invoke() 方法;而檢查應(yīng)用程序是否正在重載的工作移到了 StandardContextValve.invoke() 方法中;
【2】StandardContextMapper 類 1)對(duì)于每個(gè)引入的http 請(qǐng)求,都會(huì)調(diào)用 StandardContext實(shí)例的管道對(duì)象的基礎(chǔ)閥的invoke() 方法來處理;
1.1)StandardContext 實(shí)例的基礎(chǔ)閥:是 org.apache.catalina.core.StandardContextValve 類的實(shí)例; 1.2)StandardContextValve.invoke()方法要做的第一件事:是獲取一個(gè)要處理 http 請(qǐng)求 的Wrapper 實(shí)例; 2)在tomcat4中,StandardContextValve實(shí)例在它包含的?StandardContext中查找。StandardContextValve實(shí)例使用?StandardContext實(shí)例的映射器找到一個(gè)合適的 Wrapper實(shí)例。獲得Wrapper實(shí)例后,它就會(huì)調(diào)用 Wrapper.invoke()方法; 3)我們談?wù)動(dòng)成淦?#xff1a; ContainerBase類是 StandardContext類的父類,前者定義 addDefaultMapper()方法用來添加一個(gè)默認(rèn)的映射器,如下所示: protected void addDefaultMapper(String mapperClass) { // org.apache.catalina.core.ContainerBase.addDefaultMapper() method.// Do we need a default Mapper?if (mapperClass == null)return;if (mappers.size() >= 1)return;// Instantiate and add a default Mappertry {Class clazz = Class.forName(mapperClass);Mapper mapper = (Mapper) clazz.newInstance();mapper.setProtocol("http");addMapper(mapper);} catch (Exception e) {log(sm.getString("containerBase.addDefaultMapper", mapperClass),e);}} 3.2)StandardContext.start()方法中會(huì)調(diào)用 addDefaultMapper()方法,并傳入變量mapperClass的值: public synchronized void start() throws LifecycleException {// ......if (ok) {try {addDefaultMapper(this.mapperClass);started = true;} //...... // private String mapperClass = "org.apache.catalina.core.StandardContextMapper";
3.3)必須要調(diào)用映射器的setContainer()方法,通過傳入一個(gè)容器的實(shí)例,將映射器和容器相關(guān)聯(lián)。在Catalina中,org.apache.catalina.Mapper 接口的實(shí)現(xiàn)類是 org.apache.catalina.core.StandardContextMapper類 。StandardContextMapper實(shí)例只能與 Context級(jí)容器相關(guān)聯(lián),setContainer()方法如下所示: public void setContainer(Container container) { // org.apche.catalina.core.StandardContextMapper.setContainer().if (!(container instanceof StandardContext))throw new IllegalArgumentException(sm.getString("httpContextMapper.container"));context = (StandardContext) container;}public StandardContext() { // 而在StandardContext構(gòu)造中調(diào)用setContainer().super();pipeline.setBasic(new StandardContextValve());namingResources.setContainer(this);}
3.4)映射器最重要的方法是map()方法,該方法會(huì)返回用來處理http 請(qǐng)求的子容器,該方法簽名如下:? public Container map(Request request, boolean update) { // org.apache.catalina.core.StandardContextMapper.map().int debug = context.getDebug();// Has this request already been mapped?if (update && (request.getWrapper() != null))return (request.getWrapper());// Identify the context-relative URI to be mappedString contextPath =((HttpServletRequest) request.getRequest()).getContextPath();String requestURI = ((HttpRequest) request).getDecodedRequestURI();String relativeURI = requestURI.substring(contextPath.length());// Apply the standard request URI mapping rules from the specificationWrapper wrapper = null;String servletPath = relativeURI;String pathInfo = null;String name = null;// Rule 1 -- Exact Match // Rule 2 -- Prefix Match // Rule 3 -- Extension Match // Rule 4 -- Default Match // Update the Request (if requested) and return this Wrapper if ((debug >= 1) && (wrapper != null))context.log(" Mapped to servlet '" + wrapper.getName() +"' with servlet path '" + servletPath +"' and path info '" + pathInfo +"' and update=" + update);if (update) {request.setWrapper(wrapper);((HttpRequest) request).setServletPath(servletPath);((HttpRequest) request).setPathInfo(pathInfo);}return (wrapper);} } 4)現(xiàn)在,我們回到org.apche.catalina.core.StandardContextValve:其invoke()方法對(duì)于引入的每個(gè)http 請(qǐng)求,都會(huì)調(diào)用Context容器的map()方法,并傳入一個(gè) org.apache.catalina.Request 對(duì)象。 public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { <span style="font-family: Arial, Helvetica, sans-serif;">//org.apche.catalina.core.StandardContextValve.invoke().</span>// ..... // Disallow any direct access to resources under WEB-INF or META-INFHttpServletRequest hreq = (HttpServletRequest) request.getRequest();String contextPath = hreq.getContextPath();String requestURI = ((HttpRequest) request).getDecodedRequestURI();String relativeURI =requestURI.substring(contextPath.length()).toUpperCase();//......Context context = (Context) getContainer();Wrapper wrapper = null;try {wrapper = (Wrapper) context.map(request, true); // highlight line.->ContainerBase.map().} //......// Ask this Wrapper to process this Requestresponse.setContext(context);wrapper.invoke(request, response); } 4.1)map() 方法會(huì)針對(duì)某個(gè)特定的協(xié)議調(diào)用 findMapper()方法返回一個(gè)映射器對(duì)象,然后調(diào)用映射器對(duì)象的map() 方法獲取 Wrapper實(shí)例; public Container map(Request request, boolean update) {// Select the Mapper we will useMapper mapper = findMapper(request.getRequest().getProtocol());if (mapper == null)return (null);// Use this Mapper to perform this mappingreturn (mapper.map(request, update)); }public Mapper findMapper(String protocol) {if (mapper != null)return (mapper); // mapper == StandardContextMapperelsesynchronized (mappers) {return ((Mapper) mappers.get(protocol));}} 4.2)下面對(duì)org.apache.catalina.core.StandardContextMapper.map()方法的調(diào)用過程進(jìn)行分析: step1)會(huì)先標(biāo)識(shí)出相對(duì)于Context的URL: step2)然后,它試圖應(yīng)用匹配規(guī)則找到一個(gè)適合的Wrapper實(shí)例;
Attention)以上代碼的都是基于tomcat4在做分析,而在tomcat5中,Mapper接口及其相關(guān)類已經(jīng)被移除了。事實(shí)上,StandardContextValve.invoke()方法會(huì)從 request對(duì)象中獲取 適合的 Wrapper實(shí)例:?Wrapper wrapper = reqeust.getWrapper(); (該Wrapper實(shí)例指明了封裝在 request對(duì)象中的映射信息) // step1 begins. public Container map(Request request, boolean update) { // org.apache.catalina.core.StandardContextMapper.map().int debug = context.getDebug();// Has this request already been mapped?if (update && (request.getWrapper() != null))return (request.getWrapper());// Identify the context-relative URI to be mappedString contextPath =((HttpServletRequest) request.getRequest()).getContextPath();String requestURI = ((HttpRequest) request).getDecodedRequestURI();String relativeURI = requestURI.substring(contextPath.length());if (debug >= 1)context.log("Mapping contextPath='" + contextPath +"' with requestURI='" + requestURI +"' and relativeURI='" + relativeURI + "'"); // step1 ends. // step2 begins. // Apply the standard request URI mapping rules from the specificationWrapper wrapper = null;String servletPath = relativeURI;String pathInfo = null;String name = null;// Rule 1 -- Exact Matchif (wrapper == null) {if (debug >= 2)context.log(" Trying exact match");if (!(relativeURI.equals("/")))name = context.findServletMapping(relativeURI);if (name != null)wrapper = (Wrapper) context.findChild(name); //highlight line.if (wrapper != null) {servletPath = relativeURI;pathInfo = null;}}// Rule 2 -- Prefix Matchif (wrapper == null) {if (debug >= 2)context.log(" Trying prefix match");servletPath = relativeURI;while (true) {name = context.findServletMapping(servletPath + "/*");if (name != null)wrapper = (Wrapper) context.findChild(name); // highlight line.if (wrapper != null) {pathInfo = relativeURI.substring(servletPath.length());if (pathInfo.length() == 0)pathInfo = null;break;}int slash = servletPath.lastIndexOf('/');if (slash < 0)break;servletPath = servletPath.substring(0, slash);}}// Rule 3 -- Extension Matchif (wrapper == null) {if (debug >= 2)context.log(" Trying extension match");int slash = relativeURI.lastIndexOf('/');if (slash >= 0) {String last = relativeURI.substring(slash);int period = last.lastIndexOf('.');if (period >= 0) {String pattern = "*" + last.substring(period);name = context.findServletMapping(pattern); // highlight line.if (name != null)wrapper = (Wrapper) context.findChild(name);if (wrapper != null) {servletPath = relativeURI;pathInfo = null;}}}}// Rule 4 -- Default Matchif (wrapper == null) {if (debug >= 2)context.log(" Trying default match");name = context.findServletMapping("/");if (name != null)wrapper = (Wrapper) context.findChild(name); //highlight line.if (wrapper != null) {servletPath = relativeURI;pathInfo = null;}} // step2 ends. // step3 begins.// Update the Request (if requested) and return this Wrapperif ((debug >= 1) && (wrapper != null))context.log(" Mapped to servlet '" + wrapper.getName() +"' with servlet path '" + servletPath +"' and path info '" + pathInfo +"' and update=" + update);if (update) {request.setWrapper(wrapper);((HttpRequest) request).setServletPath(servletPath);((HttpRequest) request).setPathInfo(pathInfo);}return (wrapper);} 對(duì)以上代碼的分析(Analysis):以上代碼演示了通過 StandardContext.start()方法如何找到映射器和利用 URI 等信息來找到相應(yīng)的Wrapper容器的;(Bootstrap.main() 設(shè)置了相應(yīng)的child)
【3】對(duì)重載的支持 1)啟用重載功能:StandardContext.reloadable 屬性指明該應(yīng)用程序是否 啟用了重載功能。當(dāng)啟用了之后,當(dāng) web.xml 文件發(fā)生變化或 WEB-INF/classes 目錄下的其中一個(gè)文件被重新編譯后,應(yīng)用程序會(huì)重載;
2)StandardContext是通過其載入器實(shí)現(xiàn)應(yīng)用程序重載的: 在 tomcat4中,StandardContext對(duì)象中的 WebappLoader類實(shí)現(xiàn)了 Loader接口,并使用另一個(gè)線程檢查 WEB-INF 目錄中的所有類和JAR 文件的時(shí)間戳。只需要調(diào)用其 setContainer()方法將 WebappLoader 對(duì)象與?StandardContext 對(duì)象相關(guān)聯(lián)就可以啟動(dòng)該檢查線程。
public synchronized void reload() { //org.apache.catalina.core.StandardContext.reload().// Validate our current component stateif (!started)throw new IllegalStateException(sm.getString("containerBase.notStarted", logName()));// Make sure reloading is enabled// if (!reloadable)// throw new IllegalStateException// (sm.getString("standardContext.notReloadable"));log(sm.getString("standardContext.reloadingStarted"));// Stop accepting requests temporarilysetPaused(true);// Binding threadClassLoader oldCCL = bindThread();// Shut down our session managerif ((manager != null) && (manager instanceof Lifecycle)) {try {((Lifecycle) manager).stop();} catch (LifecycleException e) {log(sm.getString("standardContext.stoppingManager"), e);}}// Shut down the current version of all active servletsContainer children[] = findChildren();for (int i = 0; i < children.length; i++) {Wrapper wrapper = (Wrapper) children[i];if (wrapper instanceof Lifecycle) {try {((Lifecycle) wrapper).stop();} catch (LifecycleException e) {log(sm.getString("standardContext.stoppingWrapper",wrapper.getName()),e);}}}// Shut down application event listenerslistenerStop();// Clear all application-originated servlet context attributesif (context != null)context.clearAttributes();// Shut down filtersfilterStop();if (isUseNaming()) {// StartnamingContextListener.lifecycleEvent(new LifecycleEvent(this, Lifecycle.STOP_EVENT));}// Binding threadunbindThread(oldCCL);// Shut down our application class loaderif ((loader != null) && (loader instanceof Lifecycle)) {try {((Lifecycle) loader).stop();} catch (LifecycleException e) {log(sm.getString("standardContext.stoppingLoader"), e);}}// Binding threadoldCCL = bindThread();// Restart our application class loaderif ((loader != null) && (loader instanceof Lifecycle)) {try {((Lifecycle) loader).start();} catch (LifecycleException e) {log(sm.getString("standardContext.startingLoader"), e);}}// Binding threadunbindThread(oldCCL);// Create and register the associated naming context, if internal// naming is usedboolean ok = true;if (isUseNaming()) {// StartnamingContextListener.lifecycleEvent(new LifecycleEvent(this, Lifecycle.START_EVENT));}// Binding threadoldCCL = bindThread();// Restart our application event listeners and filtersif (ok) {if (!listenerStart()) {log(sm.getString("standardContext.listenerStartFailed"));ok = false;}}if (ok) {if (!filterStart()) {log(sm.getString("standardContext.filterStartFailed"));ok = false;}}// Restore the "Welcome Files" and "Resources" context attributespostResources();postWelcomeFiles();// Restart our currently defined servletsfor (int i = 0; i < children.length; i++) {if (!ok)break;Wrapper wrapper = (Wrapper) children[i];if (wrapper instanceof Lifecycle) {try {((Lifecycle) wrapper).start();} catch (LifecycleException e) {log(sm.getString("standardContext.startingWrapper",wrapper.getName()),e);ok = false;}}}// Reinitialize all load on startup servletsloadOnStartup(children);// Restart our session manager (AFTER naming context recreated/bound)if ((manager != null) && (manager instanceof Lifecycle)) {try {((Lifecycle) manager).start();} catch (LifecycleException e) {log(sm.getString("standardContext.startingManager"), e);}}// Unbinding threadunbindThread(oldCCL);// Start accepting requests againif (ok) {log(sm.getString("standardContext.reloadingCompleted"));} else {setAvailable(false);log(sm.getString("standardContext.reloadingFailed"));}setPaused(false);// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(Context.RELOAD_EVENT, null);} 3)WebappLoader.setContainer()方法的實(shí)現(xiàn)代碼如下: public void setContainer(Container container) { // org.apache.catalina.loader.WebappLoader.setContainer().// Deregister from the old Container (if any)if ((this.container != null) && (this.container instanceof Context))((Context) this.container).removePropertyChangeListener(this);// Process this property changeContainer oldContainer = this.container;this.container = container;support.firePropertyChange("container", oldContainer, this.container);// Register with the new Container (if any)if ((this.container != null) && (this.container instanceof Context)) {setReloadable( ((Context) this.container).getReloadable() );((Context) this.container).addPropertyChangeListener(this);}} 對(duì)上述代碼的分析(Analysis):看上述代碼的 最后一個(gè)if語句塊:如果當(dāng)前容器是 Context容器,則調(diào)用 setRealoadable()方法。這說明,WebappLoader.reloadable 屬性的值與 StandardContext.reloadable 屬性的值相同;
4)下面是 WebappLoader.setReloadable() 方法的實(shí)現(xiàn)代碼: public void setReloadable(boolean reloadable) { // org.apache.catalina.loader.WebappLoader.setContainer(). setReloadable()// Process this property changeboolean oldReloadable = this.reloadable;this.reloadable = reloadable;support.firePropertyChange("reloadable",new Boolean(oldReloadable),new Boolean(this.reloadable));// Start or stop our background thread if requiredif (!started)return;if (!oldReloadable && this.reloadable)threadStart();else if (oldReloadable && !this.reloadable)threadStop();} 對(duì)上述代碼的分析(Analysis): A1)若 reloadable 從false 修改為true:則會(huì)調(diào)用 threadStart()方法;而threadStart()方法會(huì)啟動(dòng)一個(gè)專用的線程來不斷地檢查 WEB-INF 目錄下的類和 JAR 文件的時(shí)間戳; A2)若reloadable 從 true 修改為false:則調(diào)用調(diào)用 threadStop() 方法;而threadStop() 方法則會(huì)終止該線程; Attention)在tomcat5中,為支持重載功能而進(jìn)行的檢查類的時(shí)間戳的工作改由 backgroundProcess()方法執(zhí)行;
【4】backgroundProcess()方法 1)Context容器的運(yùn)行需要其他組件的支持,例如載入器和Session 管理器。通常來說,這些組件需要使用各自的線程執(zhí)行一些后臺(tái)處理程序; 2)為了節(jié)省資源,在tomcat5中, 使用了不同的方法。所有的后臺(tái)處理共享同一個(gè)線程。若某個(gè)組件 或 servlet容器需要周期性地執(zhí)行一個(gè)操作,只需要將代碼寫到其 backgroundProcess()方法中即可;(干貨——tomcat5中,所有的后臺(tái)處理共享同一個(gè)線程。) 3)這個(gè)共享線程在 ContainerBase對(duì)象中創(chuàng)建。ContainerBase.start()方法調(diào)用其 threadStart()方法啟動(dòng)該后臺(tái)線程;(Attention-這是tomcat5中的 ContainerBase.start()) public synchronized void start() throws LifecycleException { //org.apache.catalina.core.ContainerBase.start() in tomcat5.// Validate and update our current component stateif (started) {log.info(sm.getString("containerBase.alreadyStarted", logName()));return;} // Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);started = true;// Start our subordinate components, if anyif ((loader != null) && (loader instanceof Lifecycle))((Lifecycle) loader).start();getLogger();if ((logger != null) && (logger instanceof Lifecycle))((Lifecycle) logger).start();if ((manager != null) && (manager instanceof Lifecycle))((Lifecycle) manager).start();if ((cluster != null) && (cluster instanceof Lifecycle))((Lifecycle) cluster).start();if ((realm != null) && (realm instanceof Lifecycle))((Lifecycle) realm).start();if ((resources != null) && (resources instanceof Lifecycle))((Lifecycle) resources).start();// Start our child containers, if anyContainer children[] = findChildren();for (int i = 0; i < children.length; i++) {if (children[i] instanceof Lifecycle)((Lifecycle) children[i]).start();}// Start the Valves in our pipeline (including the basic), if anyif (pipeline instanceof Lifecycle)((Lifecycle) pipeline).start();// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(START_EVENT, null);// Start our threadthreadStart(); //highlight line.// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);}protected void threadStart() { //org.apache.catalina.core.ContainerBase.threadStart() in tomcat5.if (thread != null)return;if (backgroundProcessorDelay <= 0)return;threadDone = false;String threadName = "ContainerBackgroundProcessor[" + toString() + "]";thread = new Thread(new ContainerBackgroundProcessor(), threadName); //highlight line.thread.setDaemon(true);thread.start();} 4)threadStart()方法:通過傳入一個(gè)實(shí)現(xiàn)了 java.lang.Runnable接口的 ContainerBackgroundProcessor 類的實(shí)例構(gòu)造一個(gè)新線程。其ContainerBackgroundProcessor定義如下: protected class ContainerBackgroundProcessor implements Runnable { //org.apache.catalina.core.ContainerBase.ContainerBackroundProcessor class defined in tomcat 5, which is a inner class in ContainerBasepublic void run() {while (!threadDone) {try {Thread.sleep(backgroundProcessorDelay * 1000L);} catch (InterruptedException e) {;}if (!threadDone) {Container parent = (Container) getMappingObject();ClassLoader cl = Thread.currentThread().getContextClassLoader();if (parent.getLoader() != null) {cl = parent.getLoader().getClassLoader();}processChildren(parent, cl); // highlight line.}}}protected void processChildren(Container container, ClassLoader cl) {try {if (container.getLoader() != null) {Thread.currentThread().setContextClassLoader(container.getLoader().getClassLoader());}container.backgroundProcess(); // highlight line.} catch (Throwable t) {log.error("Exception invoking periodic operation: ", t);} finally {Thread.currentThread().setContextClassLoader(cl);}Container[] children = container.findChildren();for (int i = 0; i < children.length; i++) {if (children[i].getBackgroundProcessorDelay() <= 0) {processChildren(children[i], cl);}}}} 對(duì)以上代碼的分析(Analysis): A1)ContainerBackgroundProcessor?類:實(shí)際上是 ContainerBase類的內(nèi)部類; A2)在其run()方法中是一個(gè)while 循環(huán),周期性地調(diào)用其 processChildren()方法:而processChildren()方法會(huì)調(diào)用其自身對(duì)象的 backgroundProcess()方法 和其 每個(gè)子容器的 processChildren()方法; A3)通過實(shí)現(xiàn)backgroundProcess()方法,ContainerBase類的子類可以使用一個(gè)專用線程來執(zhí)行周期性任務(wù);
【1】StardardContext 配置 1)intro:創(chuàng)建了StandardContext實(shí)例后,必須調(diào)用其start() 方法來為引入的每個(gè)http 請(qǐng)求提供服務(wù); 1.1)StandardContext對(duì)象可能啟動(dòng)失敗:這時(shí)available 設(shè)置為false,該屬性表明StandardContext 對(duì)象是否可用; 1.2)若啟動(dòng)成功:available=true,則表明StandardContext 對(duì)象配置正確; 2)正確配置StandardContext后,StandardContext才能讀入并解析默認(rèn)的 web.xml文件,該文件位于 %CATALINA_HOME%/conf 目錄下,該文件的內(nèi)容會(huì)應(yīng)用到所有部署到tomcat 中的應(yīng)用程序中。這也保證了StandardContext 實(shí)例可以處理應(yīng)用程序級(jí)的web.xml 文件; 3)StandardContext.configured屬性:表明StandardContext 實(shí)例是否正確配置 3.1)StandardContext使用了一個(gè)事件監(jiān)聽器作為其配置器;(干貨——StandardContext使用了一個(gè)事件監(jiān)聽器作為其配置器,參見下圖中StandardContext.start()方法中l(wèi)ifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null)?調(diào)用) 3.2)當(dāng)調(diào)用StandardContext.start()方法時(shí),其中要做的一件事情是,觸發(fā)一個(gè)生命周期事件。該事件調(diào)用監(jiān)聽器,對(duì)StandardContext實(shí)例進(jìn)行配置; 3.3)若配置成功,則監(jiān)聽器將 configured設(shè)置為true,否則StandardContext 實(shí)例拒絕啟動(dòng),也就無法為http 請(qǐng)求提供服務(wù)了; Attention)下面的圖片借用了 “tomcat(10)安全性中章節(jié)【6.4】中Supplement-補(bǔ)充模塊”的第1張圖;
【1.1】StandardContext 類的構(gòu)造函數(shù) 1)源代碼如下: public StandardContext() { // org.apache.catalina.core.StandardContext.StandardContext().super();pipeline.setBasic(new StandardContextValve());namingResources.setContainer(this);} 2)構(gòu)造函數(shù)最重要的事情:是為?StandardContext 實(shí)例的管道對(duì)象設(shè)置基礎(chǔ)閥;
【1.2】啟動(dòng)StandardContext 實(shí)例 1)start()方法會(huì)初始化 StandardContext對(duì)象,用生命周期監(jiān)聽器配置?StandardContext實(shí)例; 1.1)StandardContext 對(duì)象可能啟動(dòng)失敗:這時(shí)available 設(shè)置為false,該屬性表明StandardContext 對(duì)象是否可用; 1.2)若啟動(dòng)成功:available=true,則表明StandardContext 對(duì)象配置正確,與其關(guān)聯(lián)的子容器和組件都正確啟動(dòng); 2)正確配置后,StandardContext實(shí)例可以準(zhǔn)備為引入的 http 請(qǐng)求提供服務(wù)了。若期間發(fā)生了錯(cuò)誤,則available設(shè)置為false; 3)StandardContext.configured屬性:表明StandardContext 實(shí)例是否正確配置 3.1)在start()方法的末尾,?StandardContext實(shí)例會(huì)檢查 configured變量的值,若configured設(shè)置為 true,則StandardContext 啟動(dòng)成功; 3.2)否則,調(diào)用stop() 方法,關(guān)閉在start() 方法已經(jīng)啟動(dòng)的所有組件; public synchronized void start() throws LifecycleException { //org.apache.catalina.core.StandardContext.start()方法if (started)throw new LifecycleException(sm.getString("containerBase.alreadyStarted", logName()));if (debug >= 1)log("Starting");// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);if (debug >= 1)log("Processing start(), current available=" + getAvailable());setAvailable(false);setConfigured(false);boolean ok = true;// Add missing components as necessaryif (webappResources == null) { // (1) Required by Loaderif (debug >= 1)log("Configuring default Resources");try {if ((docBase != null) && (docBase.endsWith(".war")))setResources(new WARDirContext());elsesetResources(new FileDirContext());} catch (IllegalArgumentException e) {log("Error initializing resources: " + e.getMessage());ok = false;}}if (ok) {if (!resourcesStart())ok = false;}// Install DefaultContext configurationif (!getOverride()) {Container host = getParent();if (host instanceof StandardHost) {((StandardHost)host).installDefaultContext(this);Container engine = host.getParent();if( engine instanceof StandardEngine ) {((StandardEngine)engine).installDefaultContext(this);}}}if (getLoader() == null) { // (2) Required by Managerif (getPrivileged()) {if (debug >= 1)log("Configuring privileged default Loader");setLoader(new WebappLoader(this.getClass().getClassLoader()));} else {if (debug >= 1)log("Configuring non-privileged default Loader");setLoader(new WebappLoader(getParentClassLoader()));}}if (getManager() == null) { // (3) After prerequisitesif (debug >= 1)log("Configuring default Manager");setManager(new StandardManager());}// Initialize character set mappergetCharsetMapper();// Post work directorypostWorkDirectory();// Reading the "catalina.useNaming" environment variableString useNamingProperty = System.getProperty("catalina.useNaming");if ((useNamingProperty != null)&& (useNamingProperty.equals("false"))) {useNaming = false;}if (ok && isUseNaming()) {if (namingContextListener == null) {namingContextListener = new NamingContextListener();namingContextListener.setDebug(getDebug());namingContextListener.setName(getNamingContextName());addLifecycleListener(namingContextListener);}}// Binding threadClassLoader oldCCL = bindThread();// Standard container startupif (debug >= 1)log("Processing standard container startup");if (ok) {try {addDefaultMapper(this.mapperClass);started = true;// Start our subordinate components, if anyif ((loader != null) && (loader instanceof Lifecycle))((Lifecycle) loader).start();if ((logger != null) && (logger instanceof Lifecycle))((Lifecycle) logger).start();// Unbinding threadunbindThread(oldCCL);// Binding threadoldCCL = bindThread();if ((cluster != null) && (cluster instanceof Lifecycle))((Lifecycle) cluster).start();if ((realm != null) && (realm instanceof Lifecycle))((Lifecycle) realm).start();if ((resources != null) && (resources instanceof Lifecycle))((Lifecycle) resources).start();// Start our Mappers, if anyMapper mappers[] = findMappers();for (int i = 0; i < mappers.length; i++) {if (mappers[i] instanceof Lifecycle)((Lifecycle) mappers[i]).start();}// Start our child containers, if anyContainer children[] = findChildren();for (int i = 0; i < children.length; i++) {if (children[i] instanceof Lifecycle)((Lifecycle) children[i]).start();}// Start the Valves in our pipeline (including the basic),// if anyif (pipeline instanceof Lifecycle)((Lifecycle) pipeline).start();// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(START_EVENT, null);if ((manager != null) && (manager instanceof Lifecycle))((Lifecycle) manager).start();} finally {// Unbinding threadunbindThread(oldCCL);}}if (!getConfigured())ok = false;// We put the resources into the servlet contextif (ok)getServletContext().setAttribute(Globals.RESOURCES_ATTR, getResources());// Binding threadoldCCL = bindThread();// Create context attributes that will be requiredif (ok) {if (debug >= 1)log("Posting standard context attributes");postWelcomeFiles();}// Configure and call application event listeners and filtersif (ok) {if (!listenerStart())ok = false;}if (ok) {if (!filterStart())ok = false;}// Unbinding threadunbindThread(oldCCL);// Set available status depending upon startup successif (ok) {if (debug >= 1)log("Starting completed");setAvailable(true);} else {log(sm.getString("standardContext.startFailed"));try {stop();} catch (Throwable t) {log(sm.getString("standardContext.startCleanup"), t);}setAvailable(false);throw new LifecycleException(sm.getString("standardContext.startFailed"));}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);// Load and initialize all "load on startup" servletsoldCCL = bindThread();loadOnStartup(findChildren());unbindThread(oldCCL);} 對(duì)以上代碼的調(diào)用過程進(jìn)行分析-start()方法需要完成以下工作(works): work1)觸發(fā) BEFORE_START 事件; public synchronized void start() throws LifecycleException { //org.apache.catalina.core.StandardContext.start()方法。if (started)throw new LifecycleException(sm.getString("containerBase.alreadyStarted", logName()));if (debug >= 1)log("Starting");// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); work2)將 availability 屬性設(shè)置為false; work3)將 configured 屬性設(shè)置為 false; if (debug >= 1)log("Processing start(), current available=" + getAvailable());setAvailable(false);setConfigured(false);boolean ok = true; work4)配置資源; // Add missing components as necessaryif (webappResources == null) { // (1) Required by Loaderif (debug >= 1)log("Configuring default Resources");try {if ((docBase != null) && (docBase.endsWith(".war")))setResources(new WARDirContext());elsesetResources(new FileDirContext());} catch (IllegalArgumentException e) {log("Error initializing resources: " + e.getMessage());ok = false;}}if (ok) {if (!resourcesStart())ok = false;}// Install DefaultContext configurationif (!getOverride()) {Container host = getParent();if (host instanceof StandardHost) {((StandardHost)host).installDefaultContext(this);Container engine = host.getParent();if( engine instanceof StandardEngine ) {((StandardEngine)engine).installDefaultContext(this);}}} work5)設(shè)置載入器; if (getLoader() == null) { // (2) Required by Managerif (getPrivileged()) {if (debug >= 1)log("Configuring privileged default Loader");setLoader(new WebappLoader(this.getClass().getClassLoader()));} else {if (debug >= 1)log("Configuring non-privileged default Loader");setLoader(new WebappLoader(getParentClassLoader()));}} work6)設(shè)置Session 管理器; if (getManager() == null) { // (3) After prerequisitesif (debug >= 1)log("Configuring default Manager");setManager(new StandardManager());} work7)初始化字符集映射器; // Initialize character set mappergetCharsetMapper(); // defined in start().public CharsetMapper getCharsetMapper() {// Create a mapper the first time it is requestedif (this.charsetMapper == null) {try {Class clazz = Class.forName(charsetMapperClass);this.charsetMapper =(CharsetMapper) clazz.newInstance();} catch (Throwable t) {this.charsetMapper = new CharsetMapper();}}return (this.charsetMapper);} work8)啟動(dòng)與該Context 容器相關(guān)聯(lián)的組件; // Post work directorypostWorkDirectory(); // Reading the "catalina.useNaming" environment variableString useNamingProperty = System.getProperty("catalina.useNaming");if ((useNamingProperty != null)&& (useNamingProperty.equals("false"))) {useNaming = false;}if (ok && isUseNaming()) {if (namingContextListener == null) {namingContextListener = new NamingContextListener();namingContextListener.setDebug(getDebug());namingContextListener.setName(getNamingContextName());addLifecycleListener(namingContextListener);}}// Binding threadClassLoader oldCCL = bindThread(); work9)啟動(dòng)子容器; work10)啟動(dòng)管道對(duì)象; work11)啟動(dòng)Session 管理器;
work12)觸發(fā) START 事件,在這里監(jiān)聽器(ContextConfig 實(shí)例)會(huì)執(zhí)行一些配置操作,若配置成功,ContextConfig 實(shí)例會(huì)將 StandardContext.configured 變量設(shè)置為 true; // Set available status depending upon startup successif (ok) {if (debug >= 1)log("Starting completed");setAvailable(true);} else {log(sm.getString("standardContext.startFailed"));try {stop();} catch (Throwable t) {log(sm.getString("standardContext.startCleanup"), t);}setAvailable(false);throw new LifecycleException(sm.getString("standardContext.startFailed"));} work13)檢查 configured 屬性的值,若為true,則調(diào)用 postWelcomePages()方法,載入那些需要在啟動(dòng)時(shí)就載入的子容器,即 Wrapper實(shí)例,將 availability屬性設(shè)置為 true。若 configured 變量為false, 則調(diào)用stop() 方法; // Create context attributes that will be requiredif (ok) { // defined in start() method.if (debug >= 1)log("Posting standard context attributes");postWelcomeFiles();}private void postWelcomeFiles() {getServletContext().setAttribute("org.apache.catalina.WELCOME_FILES",welcomeFiles);} work14)觸發(fā) AFTER_START 事件;? ? ? // Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); // highlight line.// Load and initialize all "load on startup" servletsoldCCL = bindThread();loadOnStartup(findChildren());unbindThread(oldCCL);
【1.3】 org.apahce.catalina.core.StandardContext.invoke() 方法 1)該方法首先會(huì)檢查應(yīng)用程序是否正在重載過程中,若是,則等待應(yīng)用程序重載完成。然后,它調(diào)用其父類的 ContainerBase.invoke() 方法; public void invoke(Request request, Response response) throws IOException, ServletException { // Wait if we are reloadingwhile (getPaused()) { // 返回 paused屬性的值,當(dāng)paused為true時(shí),表明應(yīng)用程序正在重載;try {Thread.sleep(1000);} catch (InterruptedException e) {;}}// Normal request processingif (swallowOutput) {try {SystemLogHandler.startCapture();super.invoke(request, response);} finally {String log = SystemLogHandler.stopCapture();if (log != null && log.length() > 0) {log(log);}}} else {super.invoke(request, response);}} Attention)上述的StandardContext.invoke() 方法是tomcat 4中的實(shí)現(xiàn),而在 tomcat5中,StandardContext 并沒有提供 invoke()方法的實(shí)現(xiàn),所以會(huì)執(zhí)行 ContainerBase.invoke() 方法;而檢查應(yīng)用程序是否正在重載的工作移到了 StandardContextValve.invoke() 方法中;
【2】StandardContextMapper 類 1)對(duì)于每個(gè)引入的http 請(qǐng)求,都會(huì)調(diào)用 StandardContext實(shí)例的管道對(duì)象的基礎(chǔ)閥的invoke() 方法來處理;
1.1)StandardContext 實(shí)例的基礎(chǔ)閥:是 org.apache.catalina.core.StandardContextValve 類的實(shí)例; 1.2)StandardContextValve.invoke()方法要做的第一件事:是獲取一個(gè)要處理 http 請(qǐng)求 的Wrapper 實(shí)例; 2)在tomcat4中,StandardContextValve實(shí)例在它包含的?StandardContext中查找。StandardContextValve實(shí)例使用?StandardContext實(shí)例的映射器找到一個(gè)合適的 Wrapper實(shí)例。獲得Wrapper實(shí)例后,它就會(huì)調(diào)用 Wrapper.invoke()方法; 3)我們談?wù)動(dòng)成淦?#xff1a; ContainerBase類是 StandardContext類的父類,前者定義 addDefaultMapper()方法用來添加一個(gè)默認(rèn)的映射器,如下所示: protected void addDefaultMapper(String mapperClass) { // org.apache.catalina.core.ContainerBase.addDefaultMapper() method.// Do we need a default Mapper?if (mapperClass == null)return;if (mappers.size() >= 1)return;// Instantiate and add a default Mappertry {Class clazz = Class.forName(mapperClass);Mapper mapper = (Mapper) clazz.newInstance();mapper.setProtocol("http");addMapper(mapper);} catch (Exception e) {log(sm.getString("containerBase.addDefaultMapper", mapperClass),e);}} 3.2)StandardContext.start()方法中會(huì)調(diào)用 addDefaultMapper()方法,并傳入變量mapperClass的值: public synchronized void start() throws LifecycleException {// ......if (ok) {try {addDefaultMapper(this.mapperClass);started = true;} //...... // private String mapperClass = "org.apache.catalina.core.StandardContextMapper";
3.3)必須要調(diào)用映射器的setContainer()方法,通過傳入一個(gè)容器的實(shí)例,將映射器和容器相關(guān)聯(lián)。在Catalina中,org.apache.catalina.Mapper 接口的實(shí)現(xiàn)類是 org.apache.catalina.core.StandardContextMapper類 。StandardContextMapper實(shí)例只能與 Context級(jí)容器相關(guān)聯(lián),setContainer()方法如下所示: public void setContainer(Container container) { // org.apche.catalina.core.StandardContextMapper.setContainer().if (!(container instanceof StandardContext))throw new IllegalArgumentException(sm.getString("httpContextMapper.container"));context = (StandardContext) container;}public StandardContext() { // 而在StandardContext構(gòu)造中調(diào)用setContainer().super();pipeline.setBasic(new StandardContextValve());namingResources.setContainer(this);}
3.4)映射器最重要的方法是map()方法,該方法會(huì)返回用來處理http 請(qǐng)求的子容器,該方法簽名如下:? public Container map(Request request, boolean update) { // org.apache.catalina.core.StandardContextMapper.map().int debug = context.getDebug();// Has this request already been mapped?if (update && (request.getWrapper() != null))return (request.getWrapper());// Identify the context-relative URI to be mappedString contextPath =((HttpServletRequest) request.getRequest()).getContextPath();String requestURI = ((HttpRequest) request).getDecodedRequestURI();String relativeURI = requestURI.substring(contextPath.length());// Apply the standard request URI mapping rules from the specificationWrapper wrapper = null;String servletPath = relativeURI;String pathInfo = null;String name = null;// Rule 1 -- Exact Match // Rule 2 -- Prefix Match // Rule 3 -- Extension Match // Rule 4 -- Default Match // Update the Request (if requested) and return this Wrapper if ((debug >= 1) && (wrapper != null))context.log(" Mapped to servlet '" + wrapper.getName() +"' with servlet path '" + servletPath +"' and path info '" + pathInfo +"' and update=" + update);if (update) {request.setWrapper(wrapper);((HttpRequest) request).setServletPath(servletPath);((HttpRequest) request).setPathInfo(pathInfo);}return (wrapper);} } 4)現(xiàn)在,我們回到org.apche.catalina.core.StandardContextValve:其invoke()方法對(duì)于引入的每個(gè)http 請(qǐng)求,都會(huì)調(diào)用Context容器的map()方法,并傳入一個(gè) org.apache.catalina.Request 對(duì)象。 public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { <span style="font-family: Arial, Helvetica, sans-serif;">//org.apche.catalina.core.StandardContextValve.invoke().</span>// ..... // Disallow any direct access to resources under WEB-INF or META-INFHttpServletRequest hreq = (HttpServletRequest) request.getRequest();String contextPath = hreq.getContextPath();String requestURI = ((HttpRequest) request).getDecodedRequestURI();String relativeURI =requestURI.substring(contextPath.length()).toUpperCase();//......Context context = (Context) getContainer();Wrapper wrapper = null;try {wrapper = (Wrapper) context.map(request, true); // highlight line.->ContainerBase.map().} //......// Ask this Wrapper to process this Requestresponse.setContext(context);wrapper.invoke(request, response); } 4.1)map() 方法會(huì)針對(duì)某個(gè)特定的協(xié)議調(diào)用 findMapper()方法返回一個(gè)映射器對(duì)象,然后調(diào)用映射器對(duì)象的map() 方法獲取 Wrapper實(shí)例; public Container map(Request request, boolean update) {// Select the Mapper we will useMapper mapper = findMapper(request.getRequest().getProtocol());if (mapper == null)return (null);// Use this Mapper to perform this mappingreturn (mapper.map(request, update)); }public Mapper findMapper(String protocol) {if (mapper != null)return (mapper); // mapper == StandardContextMapperelsesynchronized (mappers) {return ((Mapper) mappers.get(protocol));}} 4.2)下面對(duì)org.apache.catalina.core.StandardContextMapper.map()方法的調(diào)用過程進(jìn)行分析: step1)會(huì)先標(biāo)識(shí)出相對(duì)于Context的URL: step2)然后,它試圖應(yīng)用匹配規(guī)則找到一個(gè)適合的Wrapper實(shí)例;
Attention)以上代碼的都是基于tomcat4在做分析,而在tomcat5中,Mapper接口及其相關(guān)類已經(jīng)被移除了。事實(shí)上,StandardContextValve.invoke()方法會(huì)從 request對(duì)象中獲取 適合的 Wrapper實(shí)例:?Wrapper wrapper = reqeust.getWrapper(); (該Wrapper實(shí)例指明了封裝在 request對(duì)象中的映射信息) // step1 begins. public Container map(Request request, boolean update) { // org.apache.catalina.core.StandardContextMapper.map().int debug = context.getDebug();// Has this request already been mapped?if (update && (request.getWrapper() != null))return (request.getWrapper());// Identify the context-relative URI to be mappedString contextPath =((HttpServletRequest) request.getRequest()).getContextPath();String requestURI = ((HttpRequest) request).getDecodedRequestURI();String relativeURI = requestURI.substring(contextPath.length());if (debug >= 1)context.log("Mapping contextPath='" + contextPath +"' with requestURI='" + requestURI +"' and relativeURI='" + relativeURI + "'"); // step1 ends. // step2 begins. // Apply the standard request URI mapping rules from the specificationWrapper wrapper = null;String servletPath = relativeURI;String pathInfo = null;String name = null;// Rule 1 -- Exact Matchif (wrapper == null) {if (debug >= 2)context.log(" Trying exact match");if (!(relativeURI.equals("/")))name = context.findServletMapping(relativeURI);if (name != null)wrapper = (Wrapper) context.findChild(name); //highlight line.if (wrapper != null) {servletPath = relativeURI;pathInfo = null;}}// Rule 2 -- Prefix Matchif (wrapper == null) {if (debug >= 2)context.log(" Trying prefix match");servletPath = relativeURI;while (true) {name = context.findServletMapping(servletPath + "/*");if (name != null)wrapper = (Wrapper) context.findChild(name); // highlight line.if (wrapper != null) {pathInfo = relativeURI.substring(servletPath.length());if (pathInfo.length() == 0)pathInfo = null;break;}int slash = servletPath.lastIndexOf('/');if (slash < 0)break;servletPath = servletPath.substring(0, slash);}}// Rule 3 -- Extension Matchif (wrapper == null) {if (debug >= 2)context.log(" Trying extension match");int slash = relativeURI.lastIndexOf('/');if (slash >= 0) {String last = relativeURI.substring(slash);int period = last.lastIndexOf('.');if (period >= 0) {String pattern = "*" + last.substring(period);name = context.findServletMapping(pattern); // highlight line.if (name != null)wrapper = (Wrapper) context.findChild(name);if (wrapper != null) {servletPath = relativeURI;pathInfo = null;}}}}// Rule 4 -- Default Matchif (wrapper == null) {if (debug >= 2)context.log(" Trying default match");name = context.findServletMapping("/");if (name != null)wrapper = (Wrapper) context.findChild(name); //highlight line.if (wrapper != null) {servletPath = relativeURI;pathInfo = null;}} // step2 ends. // step3 begins.// Update the Request (if requested) and return this Wrapperif ((debug >= 1) && (wrapper != null))context.log(" Mapped to servlet '" + wrapper.getName() +"' with servlet path '" + servletPath +"' and path info '" + pathInfo +"' and update=" + update);if (update) {request.setWrapper(wrapper);((HttpRequest) request).setServletPath(servletPath);((HttpRequest) request).setPathInfo(pathInfo);}return (wrapper);} 對(duì)以上代碼的分析(Analysis):以上代碼演示了通過 StandardContext.start()方法如何找到映射器和利用 URI 等信息來找到相應(yīng)的Wrapper容器的;(Bootstrap.main() 設(shè)置了相應(yīng)的child)
【3】對(duì)重載的支持 1)啟用重載功能:StandardContext.reloadable 屬性指明該應(yīng)用程序是否 啟用了重載功能。當(dāng)啟用了之后,當(dāng) web.xml 文件發(fā)生變化或 WEB-INF/classes 目錄下的其中一個(gè)文件被重新編譯后,應(yīng)用程序會(huì)重載;
2)StandardContext是通過其載入器實(shí)現(xiàn)應(yīng)用程序重載的: 在 tomcat4中,StandardContext對(duì)象中的 WebappLoader類實(shí)現(xiàn)了 Loader接口,并使用另一個(gè)線程檢查 WEB-INF 目錄中的所有類和JAR 文件的時(shí)間戳。只需要調(diào)用其 setContainer()方法將 WebappLoader 對(duì)象與?StandardContext 對(duì)象相關(guān)聯(lián)就可以啟動(dòng)該檢查線程。
public synchronized void reload() { //org.apache.catalina.core.StandardContext.reload().// Validate our current component stateif (!started)throw new IllegalStateException(sm.getString("containerBase.notStarted", logName()));// Make sure reloading is enabled// if (!reloadable)// throw new IllegalStateException// (sm.getString("standardContext.notReloadable"));log(sm.getString("standardContext.reloadingStarted"));// Stop accepting requests temporarilysetPaused(true);// Binding threadClassLoader oldCCL = bindThread();// Shut down our session managerif ((manager != null) && (manager instanceof Lifecycle)) {try {((Lifecycle) manager).stop();} catch (LifecycleException e) {log(sm.getString("standardContext.stoppingManager"), e);}}// Shut down the current version of all active servletsContainer children[] = findChildren();for (int i = 0; i < children.length; i++) {Wrapper wrapper = (Wrapper) children[i];if (wrapper instanceof Lifecycle) {try {((Lifecycle) wrapper).stop();} catch (LifecycleException e) {log(sm.getString("standardContext.stoppingWrapper",wrapper.getName()),e);}}}// Shut down application event listenerslistenerStop();// Clear all application-originated servlet context attributesif (context != null)context.clearAttributes();// Shut down filtersfilterStop();if (isUseNaming()) {// StartnamingContextListener.lifecycleEvent(new LifecycleEvent(this, Lifecycle.STOP_EVENT));}// Binding threadunbindThread(oldCCL);// Shut down our application class loaderif ((loader != null) && (loader instanceof Lifecycle)) {try {((Lifecycle) loader).stop();} catch (LifecycleException e) {log(sm.getString("standardContext.stoppingLoader"), e);}}// Binding threadoldCCL = bindThread();// Restart our application class loaderif ((loader != null) && (loader instanceof Lifecycle)) {try {((Lifecycle) loader).start();} catch (LifecycleException e) {log(sm.getString("standardContext.startingLoader"), e);}}// Binding threadunbindThread(oldCCL);// Create and register the associated naming context, if internal// naming is usedboolean ok = true;if (isUseNaming()) {// StartnamingContextListener.lifecycleEvent(new LifecycleEvent(this, Lifecycle.START_EVENT));}// Binding threadoldCCL = bindThread();// Restart our application event listeners and filtersif (ok) {if (!listenerStart()) {log(sm.getString("standardContext.listenerStartFailed"));ok = false;}}if (ok) {if (!filterStart()) {log(sm.getString("standardContext.filterStartFailed"));ok = false;}}// Restore the "Welcome Files" and "Resources" context attributespostResources();postWelcomeFiles();// Restart our currently defined servletsfor (int i = 0; i < children.length; i++) {if (!ok)break;Wrapper wrapper = (Wrapper) children[i];if (wrapper instanceof Lifecycle) {try {((Lifecycle) wrapper).start();} catch (LifecycleException e) {log(sm.getString("standardContext.startingWrapper",wrapper.getName()),e);ok = false;}}}// Reinitialize all load on startup servletsloadOnStartup(children);// Restart our session manager (AFTER naming context recreated/bound)if ((manager != null) && (manager instanceof Lifecycle)) {try {((Lifecycle) manager).start();} catch (LifecycleException e) {log(sm.getString("standardContext.startingManager"), e);}}// Unbinding threadunbindThread(oldCCL);// Start accepting requests againif (ok) {log(sm.getString("standardContext.reloadingCompleted"));} else {setAvailable(false);log(sm.getString("standardContext.reloadingFailed"));}setPaused(false);// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(Context.RELOAD_EVENT, null);} 3)WebappLoader.setContainer()方法的實(shí)現(xiàn)代碼如下: public void setContainer(Container container) { // org.apache.catalina.loader.WebappLoader.setContainer().// Deregister from the old Container (if any)if ((this.container != null) && (this.container instanceof Context))((Context) this.container).removePropertyChangeListener(this);// Process this property changeContainer oldContainer = this.container;this.container = container;support.firePropertyChange("container", oldContainer, this.container);// Register with the new Container (if any)if ((this.container != null) && (this.container instanceof Context)) {setReloadable( ((Context) this.container).getReloadable() );((Context) this.container).addPropertyChangeListener(this);}} 對(duì)上述代碼的分析(Analysis):看上述代碼的 最后一個(gè)if語句塊:如果當(dāng)前容器是 Context容器,則調(diào)用 setRealoadable()方法。這說明,WebappLoader.reloadable 屬性的值與 StandardContext.reloadable 屬性的值相同;
4)下面是 WebappLoader.setReloadable() 方法的實(shí)現(xiàn)代碼: public void setReloadable(boolean reloadable) { // org.apache.catalina.loader.WebappLoader.setContainer(). setReloadable()// Process this property changeboolean oldReloadable = this.reloadable;this.reloadable = reloadable;support.firePropertyChange("reloadable",new Boolean(oldReloadable),new Boolean(this.reloadable));// Start or stop our background thread if requiredif (!started)return;if (!oldReloadable && this.reloadable)threadStart();else if (oldReloadable && !this.reloadable)threadStop();} 對(duì)上述代碼的分析(Analysis): A1)若 reloadable 從false 修改為true:則會(huì)調(diào)用 threadStart()方法;而threadStart()方法會(huì)啟動(dòng)一個(gè)專用的線程來不斷地檢查 WEB-INF 目錄下的類和 JAR 文件的時(shí)間戳; A2)若reloadable 從 true 修改為false:則調(diào)用調(diào)用 threadStop() 方法;而threadStop() 方法則會(huì)終止該線程; Attention)在tomcat5中,為支持重載功能而進(jìn)行的檢查類的時(shí)間戳的工作改由 backgroundProcess()方法執(zhí)行;
【4】backgroundProcess()方法 1)Context容器的運(yùn)行需要其他組件的支持,例如載入器和Session 管理器。通常來說,這些組件需要使用各自的線程執(zhí)行一些后臺(tái)處理程序; 2)為了節(jié)省資源,在tomcat5中, 使用了不同的方法。所有的后臺(tái)處理共享同一個(gè)線程。若某個(gè)組件 或 servlet容器需要周期性地執(zhí)行一個(gè)操作,只需要將代碼寫到其 backgroundProcess()方法中即可;(干貨——tomcat5中,所有的后臺(tái)處理共享同一個(gè)線程。) 3)這個(gè)共享線程在 ContainerBase對(duì)象中創(chuàng)建。ContainerBase.start()方法調(diào)用其 threadStart()方法啟動(dòng)該后臺(tái)線程;(Attention-這是tomcat5中的 ContainerBase.start()) public synchronized void start() throws LifecycleException { //org.apache.catalina.core.ContainerBase.start() in tomcat5.// Validate and update our current component stateif (started) {log.info(sm.getString("containerBase.alreadyStarted", logName()));return;} // Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);started = true;// Start our subordinate components, if anyif ((loader != null) && (loader instanceof Lifecycle))((Lifecycle) loader).start();getLogger();if ((logger != null) && (logger instanceof Lifecycle))((Lifecycle) logger).start();if ((manager != null) && (manager instanceof Lifecycle))((Lifecycle) manager).start();if ((cluster != null) && (cluster instanceof Lifecycle))((Lifecycle) cluster).start();if ((realm != null) && (realm instanceof Lifecycle))((Lifecycle) realm).start();if ((resources != null) && (resources instanceof Lifecycle))((Lifecycle) resources).start();// Start our child containers, if anyContainer children[] = findChildren();for (int i = 0; i < children.length; i++) {if (children[i] instanceof Lifecycle)((Lifecycle) children[i]).start();}// Start the Valves in our pipeline (including the basic), if anyif (pipeline instanceof Lifecycle)((Lifecycle) pipeline).start();// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(START_EVENT, null);// Start our threadthreadStart(); //highlight line.// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);}protected void threadStart() { //org.apache.catalina.core.ContainerBase.threadStart() in tomcat5.if (thread != null)return;if (backgroundProcessorDelay <= 0)return;threadDone = false;String threadName = "ContainerBackgroundProcessor[" + toString() + "]";thread = new Thread(new ContainerBackgroundProcessor(), threadName); //highlight line.thread.setDaemon(true);thread.start();} 4)threadStart()方法:通過傳入一個(gè)實(shí)現(xiàn)了 java.lang.Runnable接口的 ContainerBackgroundProcessor 類的實(shí)例構(gòu)造一個(gè)新線程。其ContainerBackgroundProcessor定義如下: protected class ContainerBackgroundProcessor implements Runnable { //org.apache.catalina.core.ContainerBase.ContainerBackroundProcessor class defined in tomcat 5, which is a inner class in ContainerBasepublic void run() {while (!threadDone) {try {Thread.sleep(backgroundProcessorDelay * 1000L);} catch (InterruptedException e) {;}if (!threadDone) {Container parent = (Container) getMappingObject();ClassLoader cl = Thread.currentThread().getContextClassLoader();if (parent.getLoader() != null) {cl = parent.getLoader().getClassLoader();}processChildren(parent, cl); // highlight line.}}}protected void processChildren(Container container, ClassLoader cl) {try {if (container.getLoader() != null) {Thread.currentThread().setContextClassLoader(container.getLoader().getClassLoader());}container.backgroundProcess(); // highlight line.} catch (Throwable t) {log.error("Exception invoking periodic operation: ", t);} finally {Thread.currentThread().setContextClassLoader(cl);}Container[] children = container.findChildren();for (int i = 0; i < children.length; i++) {if (children[i].getBackgroundProcessorDelay() <= 0) {processChildren(children[i], cl);}}}} 對(duì)以上代碼的分析(Analysis): A1)ContainerBackgroundProcessor?類:實(shí)際上是 ContainerBase類的內(nèi)部類; A2)在其run()方法中是一個(gè)while 循環(huán),周期性地調(diào)用其 processChildren()方法:而processChildren()方法會(huì)調(diào)用其自身對(duì)象的 backgroundProcess()方法 和其 每個(gè)子容器的 processChildren()方法; A3)通過實(shí)現(xiàn)backgroundProcess()方法,ContainerBase類的子類可以使用一個(gè)專用線程來執(zhí)行周期性任務(wù);
5)tomcat5 中 StandardContext.backgroundProcess()方法的實(shí)現(xiàn)如下:
public void backgroundProcess() { //org.apache.catalina.core.ContainerBase.backgroundProcess() in tomcat 5.if (!started)return;if (cluster != null) {try {cluster.backgroundProcess();} catch (Exception e) {log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e); }}if (loader != null) {try {loader.backgroundProcess();} catch (Exception e) {log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e); }}if (manager != null) {try {manager.backgroundProcess();} catch (Exception e) {log.warn(sm.getString("containerBase.backgroundProcess.manager", manager), e); }}if (realm != null) {try {realm.backgroundProcess();} catch (Exception e) {log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e); }}Valve current = pipeline.getFirst();while (current != null) {try {current.backgroundProcess();} catch (Exception e) {log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e); }current = current.getNext();}lifecycle.fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);}
總結(jié)
以上是生活随笔為你收集整理的tomcat(12)org.apache.catalina.core.StandardContext源码剖析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云电脑是什么天翼云电脑是什么
- 下一篇: 制作数字人影视解说步骤如何制作影视解说