我们已经知道,在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 ( #改为@ )