zoukankan      html  css  js  c++  java
  • How Tomcat Works(十四)

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

    本文链接http://www.cnblogs.com/chenying99/p/3242278.html

  • 相关阅读:
    BZOJ3105:[CQOI2013]新Nim游戏(线性基,贪心)
    BZOJ5102:[POI2018]Prawnicy(贪心,堆)
    BZOJ3533:[SDOI2014]向量集(线段树,三分,凸包)
    BZOJ3569:DZY Loves Chinese II(线性基)
    BZOJ3534:[SDOI2014]重建(矩阵树定理)
    【BZOJ 1202】 [HNOI2005]狡猾的商人
    【BZOJ 1003】 [ZJOI2006]物流运输trans
    【BZOJ 2321】 [BeiJing2011集训]星器
    【BZOJ 1010】 [HNOI2008]玩具装箱toy
    【BZOJ 2730】 [HNOI2012]矿场搭建
  • 原文地址:https://www.cnblogs.com/chenying99/p/3242278.html
Copyright © 2011-2022 走看看