本文接下來分析Context容器,Context容器實例表示一個具體的Web應用程序,其中包括一個或多個Wrapper實例;不過Context容器還需要其他的組件支持,典型的如載入器和Session管理器等。
在創建StandardContext實例后,必須調用其start()方法來為引入的每個HTTP請求服務;其中包括讀取和解析默認的web.xml文件(該文件位于%CATALINA_HOME%/conf目錄),該文件的內容會應用到所有部署到tomcat中的應用程序中;此外,還會配置驗證器閥和許可閥。
StandardContext類使用一個事件監聽器來作為其配置器(前面我們已經學過在SimpleContextConfig事件監聽器中配置驗證器閥)
public synchronized void start() throws LifecycleException { if (started) throw new LifecycleException (sm.getString( "containerBase.alreadyStarted" , logName())); if (debug >= 1 ) log( "Starting" ); // Notify our interested LifecycleListeners lifecycle.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 necessary if (getResources() == null ) { // (1) Required by Loader if (debug >= 1 ) log( "Configuring default Resources" ); try { if ((docBase != null ) && (docBase.endsWith(".war" ))) setResources( new WARDirContext()); else setResources( new FileDirContext()); } catch (IllegalArgumentException e) { log( "Error initializing resources: " + e.getMessage()); ok = false ; } } if (ok && (resources instanceof ProxyDirContext)) { DirContext dirContext = ((ProxyDirContext) resources).getDirContext(); if ((dirContext != null ) && (dirContext instanceof BaseDirContext)) { ((BaseDirContext) dirContext).setDocBase(getBasePath()); ((BaseDirContext) dirContext).allocate(); } } if (getLoader() == null ) { // (2) Required by Manager if (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 prerequisites if (debug >= 1 ) log( "Configuring default Manager" ); setManager( new StandardManager()); } // Initialize character set mapper getCharsetMapper(); // Post work directory postWorkDirectory(); // Reading the "catalina.useNaming" environment variable String 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 thread ClassLoader oldCCL = bindThread(); // Standard container startup if (debug >= 1 ) log( "Processing standard container startup" ); if (ok) { try { addDefaultMapper( this .mapperClass); started = true ; // Start our subordinate components, if any if ((loader != null ) && (loader instanceof Lifecycle)) ((Lifecycle) loader).start(); if ((logger != null ) && (logger instanceof Lifecycle)) ((Lifecycle) logger).start(); // Unbinding thread unbindThread(oldCCL); // Binding thread oldCCL = 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 any Mapper mappers[] = findMappers(); for ( int i = 0; i < mappers.length; i++ ) { if (mappers[i] instanceof Lifecycle) ((Lifecycle) mappers[i]).start(); } // Start our child containers, if any Container 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 any if (pipeline instanceof Lifecycle) ((Lifecycle) pipeline).start(); // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(START_EVENT, null ); if ((manager != null ) && (manager instanceof Lifecycle)) ((Lifecycle) manager).start(); } finally { // Unbinding thread unbindThread(oldCCL); } } if (! getConfigured()) ok = false ; // We put the resources into the servlet context if (ok) getServletContext().setAttribute (Globals.RESOURCES_ATTR, getResources()); // Binding thread oldCCL = bindThread(); // Create context attributes that will be required if (ok) { if (debug >= 1 ) log( "Posting standard context attributes" ); postWelcomeFiles(); } // Configure and call application event listeners and filters if (ok) { if (! listenerStart()) ok = false ; } if (ok) { if (! filterStart()) ok = false ; } // Load and initialize all "load on startup" servlets if (ok) loadOnStartup(findChildren()); // Unbinding thread unbindThread(oldCCL); // Set available status depending upon startup success if (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 ); } // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null ); }
在它的start()方法里面,包括初始化相關容器組件、觸發相關事件等(ContextConfig監聽器會執行一些配置操作)
StandardContext類的invoke()方法由與其相關聯的連接器調用,或者當StandardContext實例是Host容器的子容器時,由Host實例的invoke()方法調用
public void invoke(Request request, Response response) throws IOException, ServletException { // Wait if we are reloading while (getPaused()) { try { Thread.sleep( 1000 ); } catch (InterruptedException e) { ; } } // Normal request processing if (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); } }
對于每個引入的HTTP請求,都會調用StandardContext實例的管道對象的基礎閥的invoke()方法來處理,這里是org.apache.catalina.core.StandardContextValve類的實例;StandardContextValve類的invoke()方法要做的第一件事是獲取一個要處理當前HTTP請求的Wrapper實例;StandardContextValve實例使用StandardContext實例的映射器找到一個合適的Wrapper實例,找到Wrapper實例后,它就會調用Wrapper實例的invoke()方法
下面是standardContextMapper類的map()方法
public Container map(Request request, boolean update) { 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 mapped String 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 + "'" ); // Apply the standard request URI mapping rules from the specification Wrapper wrapper = null ; String servletPath = relativeURI; String pathInfo = null ; String name = null ; // Rule 1 -- Exact Match if (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); if (wrapper != null ) { servletPath = relativeURI; pathInfo = null ; } } // Rule 2 -- Prefix Match if (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); 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 Match if (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); if (name != null ) wrapper = (Wrapper) context.findChild(name); if (wrapper != null ) { servletPath = relativeURI; pathInfo = null ; } } } } // Rule 4 -- Default Match if (wrapper == null ) { if (debug >= 2 ) context.log( " Trying default match" ); name = context.findServletMapping("/" ); if (name != null ) wrapper = (Wrapper) context.findChild(name); if (wrapper != null ) { servletPath = relativeURI; pathInfo = null ; } } // 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); }
standardContextMapper實例必須與一個Context級的容器相關聯(在它的map()方法中調用了Context容器實例的相關方法)
standardContext類定義了reloadable屬性來指明該應用程序是否啟用了重載功能,當啟用重載功能后,當web.xml文件發生變化或WEB-INF/classes目錄下的文件被重新編譯后,應用程序會重載。
standardContext類是通過其載入器實現應用程序重載的,在tomcat4中,standardContext對象中的WebappLoader類實現了Loader接口,并使用另一個線程檢查WEB-INF目錄中的所有類和JAR文件的時間戳。只需要調用其setContainer()方法將WebappLoader對象與standardContext對象相關聯就可以啟動該檢查線程
下面是tomcat4中WebappLoader類的setContainer()方法的實現代碼
public void setContainer(Container container) { // Deregister from the old Container (if any) if (( this .container != null ) && ( this .container instanceof Context)) ((Context) this .container).removePropertyChangeListener( this ); // Process this property change Container 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 ); } }
WebappLoader實例的reloadable屬性值與standardContext實例的reloadable屬性值是一致的
下面是WebappLoader類的setReloadable()方法的實現代碼:
public void setReloadable( boolean reloadable) { // Process this property change boolean oldReloadable = this .reloadable; this .reloadable = reloadable; support.firePropertyChange( "reloadable" , new Boolean(oldReloadable), new Boolean( this .reloadable)); // Start or stop our background thread if required if (! started) return ; if (!oldReloadable && this .reloadable) threadStart(); else if (oldReloadable && ! this .reloadable) threadStop(); }
里面的threadStart()方法會啟動一個專用的線程來不斷地檢查WEB-INF目錄下的類和JAR文件的時間戳,而threadStop()方法則會終止該線程。
---------------------------------------------------------------------------?
本系列How Tomcat Works系本人原創?
轉載請注明出處 博客園 刺猬的溫馴?
本人郵箱: ? chenying998179 # 163.com ( #改為@ )
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
