Tomcat源码分析(四)--容器处理链接之责任链模式

目标:在这篇文章希望搞明白connector.getContainer().invoke(request,response);调用容器的invoke后是怎么传递到 servlet或者jsp的?

由上篇文章Tomcat源码分析(三)--连接器是如何与容器关联的?可知,connector.getContainer()得到的容器应该是StandardEngine(其实应该是由server.xml文件配置得到的,这里先假定是StandardEngine),StandardEngine没有invoke方法,它继承与ContainerBase(事实上所有的容器都继承于ContainerBase,在ContainerBase类有一些容器的公用方法和属性),抽象类ContainerBase的invoke方法如下:

protected Pipeline pipeline = new StandardPipeline(this);//标准管道的实现StandardPipeline
 public void invoke(Request request, Response response)
        throws IOException, ServletException {
        pipeline.invoke(request, response);//调用管道里的invoke
    }

由代码可知ContainerBase的invoke方法是传递到Pipeline,调用了Pipeline的invoke方法。这里要说一下Pipeline这个类,这是一个管道类,每一个管道类Pipeline包含数个阀类,阀类是实现了Valve接口的类,Valve接口声明了invoke方法。管道和阀的概念跟servlet编程里面的过滤器机制非常像,管道就像过滤器链,阀就好比是过滤器。不过管道中还有一个基础阀的概念,所谓基础阀就是在管道中当管道把所有的普通阀都调用完成后再调用的。不管是普通阀还是基础阀,都实现了Value接口,也都继承于抽象类ValveBase。在tomcat中,当调用了管道的invoke方法,管道则会顺序调用它里面的阀的invoke方法。先看看管道StandardPipeline的invoke方法:

public void invoke(Request request, Response response)
        throws IOException, ServletException {
        // Invoke the first Valve in this pipeline for this request
        (new StandardPipelineValveContext()).invokeNext(request, response);
    }

其中StandardPipelineValveContext是管道里的一个内部类,内部类的作用是帮助管道顺序调用阀Value的invoke方法,下面看它的定义代码:

protected class StandardPipelineValveContext
        implements ValveContext {
        protected int stage = 0;
        public String getInfo() {
            return info;
        }
        public void invokeNext(Request request, Response response)
            throws IOException, ServletException {
            int subscript = stage;//阀的访问变量
            stage = stage + 1;//当前访问到第几个阀
            // Invoke the requested Valve for the current request thread
            if (subscript < valves.length) {
                valves[subscript].invoke(request, response, this);//管道的阀数组
            } else if ((subscript == valves.length) && (basic != null)) {
                basic.invoke(request, response, this);//当基础阀调用完成后,调用管道的基础阀的invoke阀
            } else {
                throw new ServletException
                    (sm.getString("standardPipeline.noValve"));
            }
        }
    }

内部类StandardPipelineValveContext的invokeNext方法通过使用局部变量来访问下一个管道数组,管道类的变量stage保存当前访问到第几个阀,valves保存管道的所有阀,在调用普通阀的invoke方法是,会把内部类StandardPipelineValveContext本身传进去,这样在普通阀中就能调用invokeNext方法以便访问下一个阀的invoke方法,下面看一个普通阀的invoke方法:

public void invoke(Request request, Response response, ValveContext valveContext)
    throws IOException, ServletException {
    // Pass this request on to the next valve in our pipeline
    valveContext.invokeNext(request, response);//使用调用下一个阀的invoke方法
    System.out.println("Client IP Logger Valve");
    ServletRequest sreq = request.getRequest();
    System.out.println(sreq.getRemoteAddr());
    System.out.println("------------------------------------");
  }

这个阀的invoke方法,通过传进来到StandardPipelineValveContext(实现了ValveContext接口)的invokeNext方法来实现调用下一个阀的invoke方法。然后简单的打印了请求的ip地址。

最后再看StandardPipelineValveContext的invokeNext方法,调用完普通阀数组valves的阀后,开始调用基础阀basic的invoke方法,这里先说基础阀的初始化,在每一个容器的构造函数类就已经初始化了基础阀,看容器StandardEngine的构造函数:

public StandardEngine() {
        super();
        pipeline.setBasic(new StandardEngineValve());//容器StandardEngine的基础阀StandardEngineValve
    }

即在容器构造的时候就已经把基础阀添加进管道pipeline中,这样在StandardPipelineValveContext中的invokeNext方法里就能调用基础阀的invoke了,当basic.invoke(request, response, this);进入基础阀StandardEngineValve,看基础阀StandardEngineValve的invoke方法:

public void invoke(Request request, Response response,
                       ValveContext valveContext)
        throws IOException, ServletException {
       ...........................

        // Ask this Host to process this request
        host.invoke(request, response);

    }

这里省略了很多代码,主要是为了更加理解调用逻辑,在StandardEngine的基础阀StandardEngineValve里,调用了子容器invoke方法(这里子容器就是StandardHost),还记得一开始connector.invoke(request, response)(即StandardEngine的invoke方法)现在顺利的传递到子容器StandardHost的invoke方法,变成了StandardHost.invoke(request, response)。由此可以猜测StandardHost也会传递给它的子容器,最后传递到最小的容器StandardWrapper的invoke方法,然后调用StandardWrapper的基础阀StandardWrapperValue的invoke方法,由于StandardWrapper是最小的容器了,不能再传递到其他容器的invoke方法了,那它的invoke方法做了什么?主要做了两件事, 1:创建一个过滤器链并 2:分配一个servlet或者jsp,主要代码如下:

StandardWrapperValue的invoke方法
            servlet = wrapper.allocate();  //分配一个servlet
....................................................................
         // Create the filter chain for this request
            ApplicationFilterChain filterChain =
            createFilterChain(request, servlet);
.........................................................
            String jspFile = wrapper.getJspFile();//分配一个jsp
            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和sres是request和response的包装类,在这里面会调用servlet的services方法
            }

这里先不关注jsp,只关注一下servlet,通过servlet = wrapper.allocate(); 进入StandardWrapper的allocate方法,allocate主要就是调用了loadServlet方法,在loadServlet方法类用tomcat自己的类加载器实例化了一个servlet对象,并调用了该servlet的init和service方法:

StandardWrapper的loadServlet方法(这里省略了很多其他的代码)
 Servlet servlet = null;
String actualClass = servletClass;//servlet的字节码字符串
 Loader loader = getLoader();
ClassLoader classLoader = loader.getClassLoader();//得到类加载器
 Class classClass = null;
                if (classLoader != null) {
                    System.out.println("Using classLoader.loadClass");
                    classClass = classLoader.loadClass(actualClass);//通过类加载器实例化servlet
                } else {
                    System.out.println("Using forName");
                    classClass = Class.forName(actualClass);//通过反射实例化servlet
                }
 servlet = (Servlet) classClass.newInstance();//实例化servlet
  servlet.init(facade);//调用servlet的init
     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);
                };//调用jsp的service方法,jsp会被编译成servlet,所以也会有service方法

至此已经把请求传递到servlet的service(或者jsp的service)方法,整个处理请求到这里就结束了,剩下的就是返回客户端了。这里提出几个问题。1:那么多的servlet,tomcat是怎么知道要请求到哪个servlet?这个问题留待下篇博客再来讲吧。

相关推荐