2013-10-20java
StandardWrapper是什麼 StandardWrapper是負責對Servlet的封裝,在tomcat的結構層次中屬於最內層,跟Servlet最接近的組件,是裝載Servlet的容器,StandardWrapper沒有子容器,由於不支持addChild()方法。在StandardWrapper以前Host已經解決了Servlet的選擇問題,那麼StandardWrapper要解決的問題是加載實例化Servlet,並使之可用,可以調用開發者定義的請求處理邏輯響應請求,且要維護Servlet的上面週期,資源分配。首先先看StandardWrapper裏面所具有的信息。web
Wrapper
StandardWrapper的實現接口,是包裝Servlet的接口,定義了加載Servlet、使用Servlet和管理維護Servlet的方法,包裝類主要經過java反射方式動態加載Context所須要的Servlet,並將Servlet緩存一段時間,兩個重要的方法:load()方法用來加載Servlet,allocate()方法用來完成Servlet使用前的準備工做。apache
ContainerBase
被StandardWrapper繼承容器基類,賦予StandardWrapper容器功能。設計模式
StandardWrapper()
默認構造方法,在構造方法中,與其餘容器同樣都指定pipeline的basic類型:StandardWrapperValve,在pipeline鏈尾部,控制請求數據的流動,在StandardWrapper的功能是調用StandardWrapper獲取Servlet經過反射調用Servlet方法響應請求完成任務。緩存
facade : StandardWrapperFacade
是設計模式中外觀模式的應用,對ServletConfig封裝,簡化Servlet對ServletConifg的訪問。tomcat
servletClass : String
StandardWrapper包裝的對象,servlet類文件全報包名,用於記錄包裝類。app
swValve : StandardWrapperValve
pipeline中的basicValve,做用很大,主要功能是使用servlet的邏輯處理請求信息。jsp
getServletMethods()
工具方法,在使用servlet中須要知道servlet所具備的方法ide
上面內容都是一些類配置信息,下面的內容是關於StandardWrapper如何加載servlet並管理servlet的。首先是從磁盤上讀取servlet文件加載到虛擬機中,這步由**loadServlet()**完成工具
loadServlet()
使用這個方法來將磁盤上的servlet類文件加載到JVM中,並完成實例化。在loadServlet中主要步驟以下:
至此,StandardWrapper已經完成servlet的裝載和實例化操做,下面是loadServlet()方法源碼
/** * Load and initialize an instance of this servlet, if there is not already * at least one initialized instance. This can be used, for example, to load * servlets that are marked in the deployment descriptor to be loaded at * server startup time. * 裝載和初始化一個servlet實例, */ 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; if (swallowOutput) { SystemLogHandler.startCapture(); } Servlet servlet; try { long t1 = System.currentTimeMillis(); // 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(); // Merge init parameters String paramNames[] = jspWrapper.findInitParameters(); for (int i = 0; i < paramNames.length; i++) { if (parameters.get(paramNames[i]) == null) { parameters .put(paramNames[i], jspWrapper .findInitParameter(paramNames[i])); } } } } // 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) && !((Context) getParent()).getPrivileged()) { // If it is a priviledged context - using its own // class loader will work, since it's a child of the container // loader classLoader = this.getClass().getClassLoader(); } // Load the specified servlet class from the appropriate class // loader Class classClass = null; try { if (SecurityUtil.isPackageProtectionEnabled()) { final ClassLoader fclassLoader = classLoader; final String factualClass = actualClass; try { classClass = (Class) AccessController .doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { if (fclassLoader != null) { return fclassLoader .loadClass(factualClass); } else { return Class.forName(factualClass); } } }); } catch (PrivilegedActionException pax) { Exception ex = pax.getException(); if (ex instanceof ClassNotFoundException) { throw (ClassNotFoundException) ex; } else { getServletContext().log( "Error loading " + fclassLoader + " " + factualClass, ex); } } } else { if (classLoader != null) { classClass = classLoader.loadClass(actualClass); } else { classClass = Class.forName(actualClass); } } } catch (ClassNotFoundException e) { unavailable(null); getServletContext().log( "Error loading " + classLoader + " " + actualClass, e); 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(); // Annotation processing if (!((Context) getParent()).getIgnoreAnnotations()) { if (getParent() instanceof StandardContext) { ((StandardContext) getParent()) .getAnnotationProcessor().processAnnotations( servlet); ((StandardContext) getParent()) .getAnnotationProcessor() .postConstruct(servlet); } } } catch (ClassCastException e) { unavailable(null); // Restore the context ClassLoader throw new ServletException(sm.getString( "standardWrapper.notServlet", actualClass), e); } catch (Throwable e) { unavailable(null); // Added extra log statement for Bugzilla 36630: // http://issues.apache.org/bugzilla/show_bug.cgi?id=36630 if (log.isDebugEnabled()) { log.debug(sm.getString("standardWrapper.instantiate", actualClass), e); } // 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) || ((Context) getParent()) .getPrivileged())) { ((ContainerServlet) servlet).setWrapper(this); } classLoadTime = (int) (System.currentTimeMillis() - t1); // Call the initialization method of this servlet try { instanceSupport.fireInstanceEvent( InstanceEvent.BEFORE_INIT_EVENT, servlet); if (Globals.IS_SECURITY_ENABLED) { boolean success = false; try { Object[] args = new Object[] { facade }; SecurityUtil.doAsPrivilege("init", servlet, classType, args); success = true; } finally { if (!success) { // destroy() will not be called, thus clear the // reference now SecurityUtil.remove(servlet); } } } else { servlet.init(facade); } // Invoke jspInit on JSP pages if ((loadOnStartup >= 0) && (jspFile != null)) { // Invoking jspInit DummyRequest req = new DummyRequest(); req.setServletPath(jspFile); req.setQueryString(Constants.PRECOMPILE + "=true"); DummyResponse res = new DummyResponse(); if (Globals.IS_SECURITY_ENABLED) { Object[] args = new Object[] { req, res }; SecurityUtil.doAsPrivilege("service", servlet, classTypeUsedInService, args); args = null; } else { 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) { getServletContext().log("StandardWrapper.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); loadTime = System.currentTimeMillis() - t1; } finally { if (swallowOutput) { 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的實例已經能夠使用,但必須將其標記爲可用,纔會被Context使用到。經過**allocate()**方法完成這個步驟。
allocate()
這個方法負責獲取被StandardWrapper包裝的Servlet對象實例,爲Servlet可以被使用作最後的操做,主要步驟以下:
/** * Allocate an initialized instance of this Servlet that is ready to have * its <code>service()</code> method called. If the servlet class does not * implement <code>SingleThreadModel</code>, the (only) initialized instance * may be returned immediately. If the servlet class implements * <code>SingleThreadModel</code>, the Wrapper implementation must ensure * that this instance is not allocated again until it is deallocated by a * call to <code>deallocate()</code>. * * @exception ServletException * if the servlet init() method threw an exception * @exception ServletException * if a loading error occurs */ public Servlet allocate() throws ServletException { // If we are currently unloading this servlet, throw an exception if (unloading) throw new ServletException(sm.getString( "standardWrapper.unloading", getName())); boolean newInstance = false; // 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 { if (log.isDebugEnabled()) log.debug("Allocating non-STM instance"); instance = loadServlet(); // For non-STM, increment here to prevent a race // condition with unload. Bug 43683, test case #3 if (!singleThreadModel) { newInstance = true; countAllocated.incrementAndGet(); } } catch (ServletException e) { throw e; } catch (Throwable e) { throw new ServletException( sm.getString("standardWrapper.allocate"), e); } } } } if (!singleThreadModel) { if (log.isTraceEnabled()) log.trace(" Returning non-STM instance"); // For new instances, count will have been incremented at the // time of creation if (!newInstance) { countAllocated.incrementAndGet(); } return (instance); } } synchronized (instancePool) { while (countAllocated.get() >= 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 (log.isTraceEnabled()) log.trace(" Returning allocated STM instance"); countAllocated.incrementAndGet(); return (Servlet) instancePool.pop(); } }
這個方法真實目的是獲取StandardWrapper包裝後的Servlet類實例,有獲取狀態Servlet的方法,那麼回收Servlet的方法就由deallocate(Servlet)來完成,它的任務也很簡單,就是將已經分配的Servlet個數減一.
deallocate(Servlet)
方法負責"回收"Servlet,只作了一件事:
/** * Return this previously allocated servlet to the pool of available * instances. If this servlet class does not implement SingleThreadModel, no * action is actually required. * * @param servlet * The servlet to be returned * * @exception ServletException * if a deallocation error occurs */ public void deallocate(Servlet servlet) throws ServletException { // If not SingleThreadModel, no action is required if (!singleThreadModel) { countAllocated.decrementAndGet(); return; } // Unlock and free this instance synchronized (instancePool) { countAllocated.decrementAndGet(); instancePool.push(servlet); instancePool.notify(); } }
在瞭解StandardWrapper如何將servlet類文件加載到JVM並實例化後,接下來就須要瞭解加載控制流程了,StandardWrapper利用load()方法來控制servlet的初始化工做,在load()方法中只作了一件事:調用loadServlet()方法. load()
Servlet類在實例化用完以後,在必定的timeout過了以後,就須要清理至關於已經廢棄的Servlet所佔用的寶貴內存空間,unload()方法負責完成這個功能,下面看一下unload()方法如何完成.
unload()
負責卸載清除Servlet所佔用的內存空間,釋放所佔用資源。流程無非就是事件通知,標記狀態,卸載Servlet,詳細以下:
/** * Unload all initialized instances of this servlet, after calling the * <code>destroy()</code> method for each instance. This can be used, for * example, prior to shutting down the entire servlet engine, or prior to * reloading all of the classes from the Loader associated with our Loader's * repository. * * @exception ServletException * if an exception is thrown by the destroy() method */ public synchronized void unload() throws ServletException { // Nothing to do if we have never loaded the instance if (!singleThreadModel && (instance == null)) return; unloading = true; // Loaf a while if the current instance is allocated // (possibly more than once if non-STM) if (countAllocated.get() > 0) { int nRetries = 0; long delay = unloadDelay / 20; while ((nRetries < 21) && (countAllocated.get() > 0)) { if ((nRetries % 10) == 0) { log.info(sm.getString("standardWrapper.waiting", countAllocated.toString())); } try { Thread.sleep(delay); } catch (InterruptedException e) { ; } nRetries++; } } PrintStream out = System.out; if (swallowOutput) { SystemLogHandler.startCapture(); } // Call the servlet destroy() method try { instanceSupport.fireInstanceEvent( InstanceEvent.BEFORE_DESTROY_EVENT, instance); if (Globals.IS_SECURITY_ENABLED) { try { SecurityUtil.doAsPrivilege("destroy", instance); } finally { SecurityUtil.remove(instance); } } else { instance.destroy(); } instanceSupport.fireInstanceEvent( InstanceEvent.AFTER_DESTROY_EVENT, instance); // Annotation processing if (!((Context) getParent()).getIgnoreAnnotations()) { ((StandardContext) getParent()).getAnnotationProcessor() .preDestroy(instance); } } catch (Throwable t) { instanceSupport.fireInstanceEvent( InstanceEvent.AFTER_DESTROY_EVENT, instance, t); instance = null; instancePool = null; nInstances = 0; fireContainerEvent("unload", this); unloading = false; throw new ServletException(sm.getString( "standardWrapper.destroyException", getName()), t); } finally { // Write captured output if (swallowOutput) { String log = SystemLogHandler.stopCapture(); if (log != null && log.length() > 0) { if (getServletContext() != null) { getServletContext().log(log); } else { out.println(log); } } } } // Deregister the destroyed instance instance = null; if (singleThreadModel && (instancePool != null)) { try { while (!instancePool.isEmpty()) { Servlet s = (Servlet) instancePool.pop(); if (Globals.IS_SECURITY_ENABLED) { try { SecurityUtil.doAsPrivilege("destroy", s); } finally { SecurityUtil.remove(s); } } else { s.destroy(); } // Annotation processing if (!((Context) getParent()).getIgnoreAnnotations()) { ((StandardContext) getParent()) .getAnnotationProcessor().preDestroy(s); } } } catch (Throwable t) { instancePool = null; nInstances = 0; unloading = false; fireContainerEvent("unload", this); throw new ServletException(sm.getString( "standardWrapper.destroyException", getName()), t); } instancePool = null; nInstances = 0; } singleThreadModel = false; unloading = false; fireContainerEvent("unload", this); }
看完源碼後發現這個方法對因而singleThreadModel標記的Servlet纔有效,其餘無效,坑爹.loadServlet()在加載servlet類時須要驗證類對象類型是否爲容許類型,isServletAllowed(Object)方法完成這個功能,作法很簡單,判斷父類類型. isServletAllowed(Object)
在瞭解StandardWrapper如何裝載實例化後Servlet,做爲容器組件,StandardWrapper調用start()方法來激活組件。
start()
負責激活StandardWrapper組件,使之可用,在start()中,簡單調用super.start()即完成。
/** * Start this component, pre-loading the servlet if the load-on-startup * value is set appropriately. * * @exception LifecycleException * if a fatal error occurs during startup */ public void start() throws LifecycleException { // Send j2ee.state.starting notification if (this.getObjectName() != null) { Notification notification = new Notification("j2ee.state.starting", this.getObjectName(), sequenceNumber++); broadcaster.sendNotification(notification); } // Start up this component super.start(); if (oname != null) registerJMX((StandardContext) getParent()); // Load and initialize an instance of this servlet if requested // MOVED TO StandardContext START() METHOD setAvailable(0L); // Send j2ee.state.running notification if (this.getObjectName() != null) { Notification notification = new Notification("j2ee.state.running", this.getObjectName(), sequenceNumber++); broadcaster.sendNotification(notification); } }
StandardWrapper使用stop()方法來完成銷燬自身的功能。stop()方法中比start()的功能多了一個,就是調用unload()方法清理singleThreadModel標記的servlet對象集合。
stop()
銷燬StandardWrapper,清理servlet對象實例,中止組件.
/** * Stop this component, gracefully shutting down the servlet if it has been * initialized. * * @exception LifecycleException * if a fatal error occurs during shutdown */ public void stop() throws LifecycleException { setAvailable(Long.MAX_VALUE); // Send j2ee.state.stopping notification if (this.getObjectName() != null) { Notification notification = new Notification("j2ee.state.stopping", this.getObjectName(), sequenceNumber++); broadcaster.sendNotification(notification); } // Shut down our servlet instance (if it has been initialized) try { unload(); } catch (ServletException e) { getServletContext().log( sm.getString("standardWrapper.unloadException", getName()), e); } // Shut down this component super.stop(); // Send j2ee.state.stoppped notification if (this.getObjectName() != null) { Notification notification = new Notification("j2ee.state.stopped", this.getObjectName(), sequenceNumber++); broadcaster.sendNotification(notification); } if (oname != null) { Registry.getRegistry(null, null).unregisterComponent(oname); // Send j2ee.object.deleted notification Notification notification = new Notification("j2ee.object.deleted", this.getObjectName(), sequenceNumber++); broadcaster.sendNotification(notification); } if (isJspServlet && jspMonitorON != null) { Registry.getRegistry(null, null).unregisterComponent(jspMonitorON); } }
以上是StandardWrapper包裝Servlet的詳細過程,負責裝載Servlet到JVM中,並實例化Servlet使之可用,若是而後維護管理Servlet對象實例,包括分配Servlet對象和回收Servlet對象。StandardWrapper做爲tomcat容器組件中最小的容器,它能共享它父容器的資源容器.
堅持