本文接下来分析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 ( #改为@ )