我们已经知道,在tomcat中有四种类型的servlet容器,分别为Engine、Host、Context 和Wrapper,本文接下来对tomcat中Wrapper接口的标准实现进行说明。
对于每个引入的HTTP请求,连接器都会调用与其关联的servlet容器的invoke()方法;然后,servlet容器会调用所有子容器的invoke()方法
这里面的流程通常是servlet容器调用其管道对象的invoke()方法,其管道对象的invoke()方法最后调用其基础阀,管道对象的基础阀里面会调用子容器的invoke()方法;子容器的invoke()方法的调用序列与之相同。
在tomcat中,servlet类可以实现javax.servlet.SingleThreadModel接口,这样的servlet类也称为SingleThreadModel(STM)servlet类;根据Servlet规范,实现此接口的目的是保证servlet实例一次只处理一个请求。
StandardWrapper对象的主要任务是载入它所代表的servlet类,并进行实例化;不过StandardWrapper类并不调用servlet的service方法,该任务由StandardWrapperValve对象(StandardWrapper实例的管道对象中的基础阀)完成;StandardWrapperValve对象通过调用StandardWrapper实例的allocate()方法获取servlet实例,在获取实例后,StandardWrapperValve实例就会调用servlet实例的service()方法
对于STM 类型的servlet类与非STM 类型的servlet类,StandardWrapper实例的载入方式是不一样的;对于非STM 类型的servlet类,StandardWrapper实例只会载入一次,对于随后的请求都会返回servlet的同一个实例,它假设该servlet类的service()方法在多线程环境中是线程安全的。
而对于STM 类型的servlet类,StandardWrapper实例必须保证每一时刻只能有一个线程在执行STM servlet类的service()方法;StandardWrapper实例通过将STM 类型的servlet实例保存在一个java.util.Stack类型的栈中
public Servlet allocate() throws ServletException { if (debug >= 1) log("Allocating an instance"); // If we are currently unloading this servlet, throw an exception if (unloading) throw new ServletException (sm.getString("standardWrapper.unloading", getName())); // If not SingleThreadedModel, return the same instance every time if (!singleThreadModel) { // Load and initialize our instance if necessary if (instance == null) { synchronized (this) { if (instance == null) { try { instance = loadServlet(); } catch (ServletException e) { throw e; } catch (Throwable e) { throw new ServletException (sm.getString("standardWrapper.allocate"), e); } } } } if (!singleThreadModel) { if (debug >= 2) log(" Returning non-STM instance"); countAllocated++; return (instance); } } synchronized (instancePool) { while (countAllocated >= nInstances) { // Allocate a new instance if possible, or else wait if (nInstances < maxInstances) { try { instancePool.push(loadServlet()); nInstances++; } catch (ServletException e) { throw e; } catch (Throwable e) { throw new ServletException (sm.getString("standardWrapper.allocate"), e); } } else { try { instancePool.wait(); } catch (InterruptedException e) { ; } } } if (debug >= 2) log(" Returning allocated STM instance"); countAllocated++; return (Servlet) instancePool.pop(); } }
而在loadServlet()方法中,首先获取要加载的servlet的完整类名及类加载器,然后通过类加载器加载该servlet类,然后实例化该servlet类,最后调用该servlet实例的init()方法初始化(传入javax.servlet.ServletConfig对象)
public synchronized Servlet loadServlet() throws ServletException { // Nothing to do if we already have an instance or an instance pool if (!singleThreadModel && (instance != null)) return instance; PrintStream out = System.out; SystemLogHandler.startCapture(); Servlet servlet = null; try { // If this "servlet" is really a JSP file, get the right class. // HOLD YOUR NOSE - this is a kludge that avoids having to do special // case Catalina-specific code in Jasper - it also requires that the // servlet path be replaced by the <jsp-file> element content in // order to be completely effective String actualClass = servletClass; if ((actualClass == null) && (jspFile != null)) { Wrapper jspWrapper = (Wrapper) ((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME); if (jspWrapper != null) actualClass = jspWrapper.getServletClass(); } // Complain if no servlet class has been specified if (actualClass == null) { unavailable(null); throw new ServletException (sm.getString("standardWrapper.notClass", getName())); } // Acquire an instance of the class loader to be used Loader loader = getLoader(); if (loader == null) { unavailable(null); throw new ServletException (sm.getString("standardWrapper.missingLoader", getName())); } ClassLoader classLoader = loader.getClassLoader(); // Special case class loader for a container provided servlet if (isContainerProvidedServlet(actualClass)) { classLoader = this.getClass().getClassLoader(); log(sm.getString ("standardWrapper.containerServlet", getName())); } // Load the specified servlet class from the appropriate class loader Class classClass = null; try { if (classLoader != null) { System.out.println("Using classLoader.loadClass"); classClass = classLoader.loadClass(actualClass); } else { System.out.println("Using forName"); classClass = Class.forName(actualClass); } } catch (ClassNotFoundException e) { unavailable(null); throw new ServletException (sm.getString("standardWrapper.missingClass", actualClass), e); } if (classClass == null) { unavailable(null); throw new ServletException (sm.getString("standardWrapper.missingClass", actualClass)); } // Instantiate and initialize an instance of the servlet class itself try { servlet = (Servlet) classClass.newInstance(); } catch (ClassCastException e) { unavailable(null); // Restore the context ClassLoader throw new ServletException (sm.getString("standardWrapper.notServlet", actualClass), e); } catch (Throwable e) { unavailable(null); // Restore the context ClassLoader throw new ServletException (sm.getString("standardWrapper.instantiate", actualClass), e); } // Check if loading the servlet in this web application should be // allowed if (!isServletAllowed(servlet)) { throw new SecurityException (sm.getString("standardWrapper.privilegedServlet", actualClass)); } // Special handling for ContainerServlet instances if ((servlet instanceof ContainerServlet) && isContainerProvidedServlet(actualClass)) { System.out.println("calling setWrapper"); ((ContainerServlet) servlet).setWrapper(this); System.out.println("after calling setWrapper"); } // Call the initialization method of this servlet try { instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT, servlet); servlet.init(facade); // Invoke jspInit on JSP pages if ((loadOnStartup > 0) && (jspFile != null)) { // Invoking jspInit HttpRequestBase req = new HttpRequestBase(); HttpResponseBase res = new HttpResponseBase(); req.setServletPath(jspFile); req.setQueryString("jsp_precompile=true"); servlet.service(req, res); } instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet); } catch (UnavailableException f) { instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet, f); unavailable(f); throw f; } catch (ServletException f) { instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet, f); // If the servlet wanted to be unavailable it would have // said so, so do not call unavailable(null). throw f; } catch (Throwable f) { instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet, f); // If the servlet wanted to be unavailable it would have // said so, so do not call unavailable(null). throw new ServletException (sm.getString("standardWrapper.initException", getName()), f); } // Register our newly initialized instance singleThreadModel = servlet instanceof SingleThreadModel; if (singleThreadModel) { if (instancePool == null) instancePool = new Stack(); } fireContainerEvent("load", this); } finally { String log = SystemLogHandler.stopCapture(); if (log != null && log.length() > 0) { if (getServletContext() != null) { getServletContext().log(log); } else { out.println(log); } } } return servlet; }
StandardWrapper类的loadServlet()方法在载入servlet类后,会调用该servlet实例的init()方法,该方法需要传入一个javax.servlet.ServletConfig实例作为参数;那么, StandardWrapper对象是如何获取ServletConfig对象的呢
答案就在StandardWrapper类本身,该类不仅实现了Wrapper接口,还实现了javax.servlet.ServletConfig接口,下面是相关方法实现
/** * Return the initialization parameter value for the specified name, * if any; otherwise return <code>null</code>. * * @param name Name of the initialization parameter to retrieve */ public String getInitParameter(String name) { return (findInitParameter(name)); } /** * Return the set of initialization parameter names defined for this * servlet. If none are defined, an empty Enumeration is returned. */ public Enumeration getInitParameterNames() { synchronized (parameters) { return (new Enumerator(parameters.keySet())); } } /** * Return the servlet context with which this servlet is associated. */ public ServletContext getServletContext() { if (parent == null) return (null); else if (!(parent instanceof Context)) return (null); else return (((Context) parent).getServletContext()); } /** * Return the name of this servlet. */ public String getServletName() { return (getName()); }
StandardWrapper实例在它的loadServlet()方法里面调用它所载入的servlet类的实例的init()方法,该方法需要一个javax.servlet.ServletConfig类型参数,理论上StandardWrapper对象可以将自身传入init()方法,不过为了类型安全,StandardWrapper类将自身实例包装成StandardWrapperFacade类的一个实例,然后再传给init()方法
public final class StandardWrapperFacade implements ServletConfig { /** * Create a new facede around a StandardWrapper. */ public StandardWrapperFacade(StandardWrapper config) { super(); this.config = (ServletConfig) config; } /** * Wrapped config. */ private ServletConfig config = null; public String getServletName() { return config.getServletName(); } public ServletContext getServletContext() { ServletContext theContext = config.getServletContext(); if ((theContext != null) && (theContext instanceof ApplicationContext)) theContext = ((ApplicationContext) theContext).getFacade(); return (theContext); } public String getInitParameter(String name) { return config.getInitParameter(name); } public Enumeration getInitParameterNames() { return config.getInitParameterNames(); } }
StandardWrapperValve类是StandardWrapper实例中的基础阀,要完成两个操作
(1)执行与该servlet相关联的全部过滤器
(2)调用servlet实例的service()方法
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { // Initialize local variables we may need boolean unavailable = false; Throwable throwable = null; StandardWrapper wrapper = (StandardWrapper) getContainer(); ServletRequest sreq = request.getRequest(); ServletResponse sres = response.getResponse(); Servlet servlet = null; HttpServletRequest hreq = null; if (sreq instanceof HttpServletRequest) hreq = (HttpServletRequest) sreq; HttpServletResponse hres = null; if (sres instanceof HttpServletResponse) hres = (HttpServletResponse) sres; // Check for the application being marked unavailable if (!((Context) wrapper.getParent()).getAvailable()) { hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardContext.isUnavailable")); unavailable = true; } // Check for the servlet being marked unavailable if (!unavailable && wrapper.isUnavailable()) { log(sm.getString("standardWrapper.isUnavailable", wrapper.getName())); if (hres == null) { ; // NOTE - Not much we can do generically } else { long available = wrapper.getAvailable(); if ((available > 0L) && (available < Long.MAX_VALUE)) hres.setDateHeader("Retry-After", available); hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardWrapper.isUnavailable", wrapper.getName())); } unavailable = true; } // Allocate a servlet instance to process this request try { if (!unavailable) { servlet = wrapper.allocate(); } } catch (ServletException e) { log(sm.getString("standardWrapper.allocateException", wrapper.getName()), e); throwable = e; exception(request, response, e); servlet = null; } catch (Throwable e) { log(sm.getString("standardWrapper.allocateException", wrapper.getName()), e); throwable = e; exception(request, response, e); servlet = null; } // Acknowlege the request try { response.sendAcknowledgement(); } catch (IOException e) { sreq.removeAttribute(Globals.JSP_FILE_ATTR); log(sm.getString("standardWrapper.acknowledgeException", wrapper.getName()), e); throwable = e; exception(request, response, e); } catch (Throwable e) { log(sm.getString("standardWrapper.acknowledgeException", wrapper.getName()), e); throwable = e; exception(request, response, e); servlet = null; } // Create the filter chain for this request ApplicationFilterChain filterChain = createFilterChain(request, servlet); // Call the filter chain for this request // NOTE: This also calls the servlet's service() method try { String jspFile = wrapper.getJspFile(); if (jspFile != null) sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile); else sreq.removeAttribute(Globals.JSP_FILE_ATTR); if ((servlet != null) && (filterChain != null)) { filterChain.doFilter(sreq, sres); } sreq.removeAttribute(Globals.JSP_FILE_ATTR); } catch (IOException e) { sreq.removeAttribute(Globals.JSP_FILE_ATTR); log(sm.getString("standardWrapper.serviceException", wrapper.getName()), e); throwable = e; exception(request, response, e); } catch (UnavailableException e) { sreq.removeAttribute(Globals.JSP_FILE_ATTR); log(sm.getString("standardWrapper.serviceException", wrapper.getName()), e); // throwable = e; // exception(request, response, e); wrapper.unavailable(e); long available = wrapper.getAvailable(); if ((available > 0L) && (available < Long.MAX_VALUE)) hres.setDateHeader("Retry-After", available); hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardWrapper.isUnavailable", wrapper.getName())); // Do not save exception in 'throwable', because we // do not want to do exception(request, response, e) processing } catch (ServletException e) { sreq.removeAttribute(Globals.JSP_FILE_ATTR); log(sm.getString("standardWrapper.serviceException", wrapper.getName()), e); throwable = e; exception(request, response, e); } catch (Throwable e) { sreq.removeAttribute(Globals.JSP_FILE_ATTR); log(sm.getString("standardWrapper.serviceException", wrapper.getName()), e); throwable = e; exception(request, response, e); } // Release the filter chain (if any) for this request try { if (filterChain != null) filterChain.release(); } catch (Throwable e) { log(sm.getString("standardWrapper.releaseFilters", wrapper.getName()), e); if (throwable == null) { throwable = e; exception(request, response, e); } } // Deallocate the allocated servlet instance try { if (servlet != null) { wrapper.deallocate(servlet); } } catch (Throwable e) { log(sm.getString("standardWrapper.deallocateException", wrapper.getName()), e); if (throwable == null) { throwable = e; exception(request, response, e); } } // If this servlet has been marked permanently unavailable, // unload it and release this instance try { if ((servlet != null) && (wrapper.getAvailable() == Long.MAX_VALUE)) { wrapper.unload(); } } catch (Throwable e) { log(sm.getString("standardWrapper.unloadException", wrapper.getName()), e); if (throwable == null) { throwable = e; exception(request, response, e); } } }
上述创建过滤器链的过程本人后来作了补充:How Tomcat Works(十四)补充
---------------------------------------------------------------------------
本系列How Tomcat Works系本人原创
转载请注明出处 博客园 刺猬的温驯
本人邮箱: chenying998179#163.com (#改为@)