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