美文网首页
Tomcat学习笔记之Pipeline-Valve

Tomcat学习笔记之Pipeline-Valve

作者: 夏目手札 | 来源:发表于2019-05-07 21:50 被阅读0次

    前言

    在Container启动一篇中设计到很多关于管道的,这里做一篇详细的介绍。

    整体结构

    在[Tomcat学习笔记之启动分析(Container)(六)]一文中设计到很多Pipeline-Valve的概念,我们先用一张图来看下其整体流程:


    Pipeline-Value流程图

    由这张图我们知道管道的设计为每个容器都带来了灵活的机制,可以按照需要对不同容器添加自定义阀进行不同的逻辑处理,并且Tomcat将管道机制做成可配置形式,对于存在的阀只需通过配置文件即可,还可以自定义阀并配置就可在相应作用域内生效。四个容器中每个容器都包含自己的管道对象,管道对象用于存放若干阀门对象,他们都有自己的基础阀,且基础阀是Tomcat默认设置的,一般不可更改之,以免运行时产生问题 。

    Pipeline

    所有的管道类都会实现org.apache.catalina.Pipeline这个接口,看下这个接口中定义的方法:


    Pipeline

    一个管道包含多个阀( Valve ),这些阀共分为两类,一类叫基础阀(通过 #getBasic()、#setBasic()方法调用),一类是普通阀(通过#addValve()、#removeValve()调用)。管道都是包含在一个容器当中,所以 API 里还有 #getContainer()和#setContainer()方法。一个管道一般有一个基础阀(通过 #setBasic()方法添加),可以有 0 到多个普通阀(通过#addValve()方法添加)。

    Valve

    所有的阀类都会实现org.apache.catalina.Valve这个接口,看下这个接口中定义的方法:


    Valve

    通过#setNext()设置该阀的下一阀,通过 #getNext()返回该阀的下一个阀的引用,#invoke()方法则执行该阀内部自定义的请求处理代码,#backgroundProcess()执行周期性任务,例如重新加载等。

    StandardPipeline

    Pipeline 的默认实现类是org.apache.catalina.core.StandardPipeline。

    • 属性
       /**
         * 基础阀门
         */
        protected Valve basic = null;
        /**
         * 与此管道关联的Container
         */
        protected Container container = null;
        /**
         * 普通阀
         */
        protected Valve first = null;
    
    • 相关方法
      • #setBasic()
    public void setBasic(Valve valve) {
    
            //1. 如果新的阀与旧的一样,直接返回
            Valve oldBasic = this.basic;
            if (oldBasic == valve)
                return;
    
            //2. 停止旧的阀
            if (oldBasic != null) {
                if (getState().isAvailable() && (oldBasic instanceof Lifecycle)) {
                    try {
                        ((Lifecycle) oldBasic).stop();
                    } catch (LifecycleException e) {
                        log.error(sm.getString("standardPipeline.basic.stop"), e);
                    }
                }
                if (oldBasic instanceof Contained) {
                    try {
                        ((Contained) oldBasic).setContainer(null);
                    } catch (Throwable t) {
                        ExceptionUtils.handleThrowable(t);
                    }
                }
            }
    
            //3. 启动新的阀
            if (valve == null)
                return;
            if (valve instanceof Contained) {
                ((Contained) valve).setContainer(this.container);
            }
            if (getState().isAvailable() && valve instanceof Lifecycle) {
                try {
                    ((Lifecycle) valve).start();
                } catch (LifecycleException e) {
                    log.error(sm.getString("standardPipeline.basic.start"), e);
                    return;
                }
            }
    
            //4. 更新阀链,因为基础阀永远在链的末端
            Valve current = first;
            while (current != null) {
                if (current.getNext() == oldBasic) {
                    current.setNext(valve);
                    break;
                }
                current = current.getNext();
            }
    
            this.basic = valve;
    
        }
    

    流程很简单:停止旧阀->启动新阀->更新阀链

    • #addValve()
    public void addValve(Valve valve) {
    
            //1. 校验我们能够添加这个阀
            if (valve instanceof Contained)
                ((Contained) valve).setContainer(this.container);
    
            //2. 启动该阀
            if (getState().isAvailable()) {
                if (valve instanceof Lifecycle) {
                    try {
                        ((Lifecycle) valve).start();
                    } catch (LifecycleException e) {
                        log.error(sm.getString("standardPipeline.valve.start"), e);
                    }
                }
            }
    
            //3. 如果first为空直接令first=value,并设置next为基础阀;否则将基础阀替换成该阀,并将基础阀添加至末端
            if (first == null) {
                first = valve;
                valve.setNext(basic);
            } else {
                Valve current = first;
                while (current != null) {
                    if (current.getNext() == basic) {
                        current.setNext(valve);
                        valve.setNext(basic);
                        break;
                    }
                    current = current.getNext();
                }
            }
            //4. 通知所有的Container监听器
            container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
        }
    

    从上面知道,基础阀永远都被放在末端。#removeValve()方法将阀从普通阀链中移除,跟上面逻辑差不多,这里不贴了。

    • 子容器基础阀的设置
      • StandardEngine
    public StandardEngine() {
    
            super();
            pipeline.setBasic(new StandardEngineValve());
            /* Set the jmvRoute using the system property jvmRoute */
            try {
                setJvmRoute(System.getProperty("jvmRoute"));
            } catch(Exception ex) {
                log.warn(sm.getString("standardEngine.jvmRouteFail"));
            }
            // By default, the engine will hold the reloading thread
            backgroundProcessorDelay = 10;
    
        }
    
    • StandardHost
    public StandardHost() {
            super();
            pipeline.setBasic(new StandardHostValve());
        }
    
    • StandardContext
    public StandardContext() {
            super();
            pipeline.setBasic(new StandardContextValve());
            broadcaster = new NotificationBroadcasterSupport();
            // Set defaults
            if (!Globals.STRICT_SERVLET_COMPLIANCE) {
                // Strict servlet compliance requires all extension mapped servlets
                // to be checked against welcome files
                resourceOnlyServlets.add("jsp");
            }
        }
    
    • StandardWrapper
    public StandardWrapper() {
            super();
            swValve=new StandardWrapperValve();
            pipeline.setBasic(swValve);
            broadcaster = new NotificationBroadcasterSupport();
    
        }
    

    ValveBase

    Valve的抽象实现类是org.apache.catalina.valves.ValveBase,实现了所有阀的初始化和启动方法,最终的#invoke()方法由各个子类实现:

    • StandardEngineValve
    public final void invoke(Request request, Response response)
            throws IOException, ServletException {
    
            //1. 从request中获取host
            Host host = request.getHost();
            if (host == null) {
                // HTTP 0.9 or HTTP 1.0 request without a host when no default host
                // is defined. This is handled by the CoyoteAdapter.
                return;
            }
            if (request.isAsyncSupported()) {
                request.setAsyncSupported(host.getPipeline().isAsyncSupported());
            }
    
            //2. 调用host的普通阀的#invoke()
            host.getPipeline().getFirst().invoke(request, response);
        }
    
    • StandardHostValve
    public final void invoke(Request request, Response response)
            throws IOException, ServletException {
    
            //1. 从request获取context
            Context context = request.getContext();
            if (context == null) {
                return;
            }
            //省略部分代码。。。
            if (!response.isErrorReportRequired()) {
              //2. 调用context普通阀的invoke方法
                context.getPipeline().getFirst().invoke(request, response);
            }
          //省略部分代码。。。
    }
    
    • StandardContextValve
    public final void invoke(Request request, Response response)
            throws IOException, ServletException {
    
            //1. 禁止在WEB-INF或META-INF下直接访问资源
            MessageBytes requestPathMB = request.getRequestPathMB();
            if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
                    || (requestPathMB.equalsIgnoreCase("/META-INF"))
                    || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
                    || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
                return;
            }
    
            //2. request中获取wrapper
            Wrapper wrapper = request.getWrapper();
            if (wrapper == null || wrapper.isUnavailable()) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
                return;
            }
    
            // Acknowledge the request
            try {
                response.sendAcknowledgement();
            } catch (IOException ioe) {
                container.getLogger().error(sm.getString(
                        "standardContextValve.acknowledgeException"), ioe);
                request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                return;
            }
    
            if (request.isAsyncSupported()) {
                request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
            }
            //3. 调用wrapper普通阀的invoke
            wrapper.getPipeline().getFirst().invoke(request, response);
        }
    
    • StandardWrapperValve
    public final void invoke(Request request, Response response)
            throws IOException, ServletException {
                //省略很多代码。。。。
                //1. 分配servlet实例以处理此请求
                if (!unavailable) {
                    servlet = wrapper.allocate();
                }
                //2. 创建调用链
                ApplicationFilterChain filterChain =
                    ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
               //3. 调用调用链
                filterChain.doFilter(request.getRequest(),
                                        response.getResponse());
              //4. 释放调用链
              if (filterChain != null) {
                filterChain.release();
              }
              //5. 释放已分配的servlet实例
              if (servlet != null) {
                    wrapper.deallocate(servlet);
                }
    

    在StandardWrapperValve#invoke()方法中,分配Servlet,创建调用链,最终进入到我们的业务逻辑中。

    总结

    综上,请求就可以从连接器内一步一步流转到具体Servlet的#service()方法中。
    这里可以看出容器内的 Engine、Host、Context、Wrapper 容器组件的实现的共通点:

    • 这些组件内部都有一个成员变量 pipeline ,因为它们都是从ContainerBase类继承来的,pipeline 就定义在这个类中。所以每一个容器内部都关联了一个管道。
    • 都是在类的构造方法中设置管道内的基础阀。
    • 所有的基础阀的实现最后都会调用其下一级容器(直接从请求中获取下一级容器对象的引用,在上面的分析中已经设置了与该请求相关的各级具体组件的引用)的#getPipeline().getFirst().invoke() 方法,直到 Wrapper 组件。因为 Wrapper 是对一个 Servlet 的包装,所以它的基础阀内部调用的过滤器链的 #doFilter()方法和 Servlet 的#service()方法。

    相关文章

      网友评论

          本文标题:Tomcat学习笔记之Pipeline-Valve

          本文链接:https://www.haomeiwen.com/subject/gygknqtx.html