zoukankan      html  css  js  c++  java
  • 【深入剖析Tomcat笔记】第五篇 Tomcat Container 与 Pipelining Tasks

    简述

    在第一章开始就提到,Tomcat的本质什么?是容器。容器这个概念现在很火,一提到容器,我们立马可以想到虚拟化,SAAS,Docket。虽然在这里,我们不会深入去探究其他虚拟化相关技术中的容器思想,但Tomcat的容器究竟是什么,容器为Tomcat带来了什么,这次我们一起去探究。

    本篇单独作为DEMO运行意义不大,因此和下一篇合并提供DEMO

    Container

    ContainerStructure.png

    org.apache.catalina.Container 接口定义了容器的形式,有四种容器:

    Name Lifecycle
    Wrapper 底层容器,servlet运行容器,每个wrapper运行一个servlet
    Conetxt 包含多个Wrapper,每个Conetext对应一个servlet Project,session的管理层
    Host 虚拟主机,包含多个Context,主要用于proxy和多端口复用
    Engine 顶级容器,包含所有项目,包含多个Host

    REST设计中引入一个很重要的概念,资源。Servlet对数据的解析,本质上也是一种对资源的转换。例如,查询某一条记录,输入一组特别标志,获得特定标志对应的数据,这相当将特定标志转化为数据,这是一种资源的转化;通过URL获取一个静态页面,url地址同样是一种请求输入,而静态页面作为URL对应的静态输出,这同样是一种资源的转化。Tomcat中对于资源的转化是由servlet完成的,Tomcat很少直接参与资源的转化,Tomcat给予Servlet资源转化最主要的是提供了完善的环境,而这种环境主要就是容器Container所提供的。

    Engine是所有资源的总入口,提供了整体的容器,通过Engine可以实现多个Host虚拟主机同时存在。

    Host虚拟主机为Tomcat提供了多虚拟主机配置的功能,将一个Tomcat通过虚拟主机配置,可以划分成为逻辑独立的多台虚拟服务容器。通过虚拟主机,可以实现多域名映射多虚拟主机,虚拟主机独立数据存储,虚拟主机独立配置等。

    Context为服务提供容器,每一个在tomcat部署的服务都由一个Context提供服务,通过Context容器,Tomcat为我们提供了很多有趣的特性,其中很重要的一个特性就是Session和Cookie。HTTP虽然是无状态协议,但是通过Session和Cookie,HTTP协议实现了真正拥有上下文的交互方式。通过上下文,我们实现了有状态的资源交互。更重要的一点是,这种有趣的交互方式由容器来实现,而服务无需关心其实现。
    Wrapper为每一个Servlet提供一个对应的容器。

    一个父容器可以包含一个或多个子容器,如一个Engine可以设置多个Host。
    很明显,Contianer其实是一种组合模式(Composite Pattern)

    将对象组合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
    《设计模式–组合模式定义》

    在《设计模式》中对于组合模式实现的时候需要关注的几个点:
    1. 显式父部件引用
    2. 最大化Component接口
    3. 声明管理子部件的操作

    因此设计Container接口如下

        //添加子容器
        public void addChild(Container child);
    
        //移除子容器
        public void removeChild(Container child);
    
        //查找子容器
        public Container findChild(String childName);
    
        //子容器列表
        public Container[] findChildren();
    
        //获取父容器
        public Container getParent();
    
        //设置父容器
        public void setParent(Container parent);
    
        //获取容器名称
        public String getName();
    
        //设置容器名称
        public void setName(String name);
    

    根据Container接口,设计ContainerBase类提供如下:

    public abstract class ContainerBase implements Container {
    
        /**
         * 子容器存储
         */
        private HashMap<String, Container> children = new HashMap();
    
        /**
         * Name
         */
        private String name ;
    
        /**
         * 父容器
         */
        private Container parent;
    
        /**
         * 添加子容器
         * @param child Container
         */
        @Override
        public void addChild(Container child) {
    
            children.put(child.getName() ,child);
        }
    
        /**
         * 移除子容器
         * @param child Container
         */
        @Override
        public void removeChild(Container child) {
            children.remove(child);
        }
    
        /**
         * 查找子容器
         * @param childName
         */
        @Override
        public Container findChild(String childName) {
            return (Container) children.get(childName);
        }
    
        /**
         * 获取所有子容器
         * @return Contianer[]
         */
        @Override
        public Container[] findChildren() {
            return (Container[]) children.values().toArray();
        }
    
        /**
         * 设置父容器
         * @param parent Container
         */
        @Override
        public void setParent(Container parent) {
            this.parent.removeChild(this);
            this.parent = parent;
        }
    
        /**
         * 获取父容器
         * @return Container
         */
        @Override
        public Container getParent() {
            return parent;
        }
    
        /**
         * 获取容器名称
         * @return String name
         */
        @Override
        public String getName() {
            return this.name;
        }
    
        /**
         * 设置容器名称
         */
        @Override
        public void setName(String name) {
            this.parent.removeChild(this);
            this.name = name;
            this.parent.addChild(this);
        }
    }
    

    PipeliningTasks和Valve

    相信大家都接触过拦截器和spring AOP,实际上Pipelining Tasks也是一种AOP思想的实现方式。

    Tomcat通过Pipeline和Valve实现上述问题。

    Pipeline

    Pipeline.png

    Pipeline整体结构如上所示。
    Pipeline 设计非常形象,不知道大家有没有玩过接水管,实际上Pipeline正如管道所示。一个管道前可以设置多个阀门,但至少设置一个基础阀门。
    上一节中我们讲到Context可以包含多个Wrapper,在引入Pipeline之后的结构正如下图所示。Context包含多个Wrapper,在执行每一个Wrapper之前,先要过Context的Pipeline,同时每一个Wrapper都具有属于自己的Pipeline。

    QQ截图20170731203055.png

    我们对Pipeline进行分析,可以设计基本Pipeline接口如下

    //设置基本阀门
    public void setBasic(Valve basic);
    
    //获取基本阀门
    public Valve getBasic();
    
    //添加阀门
    public void addValve(Valve valve);
    
    //删除阀门
    public void remove(Valve valve);
    
    //获得所有非基本阀门
    public Valve[] getValves();
    
    //下节说明
    public void invoke(Request request, Response response);
    

    同理,Engine、Host、Context和Wrapper经过Pipeline可以实现类似于AOP的配置,而这个配置就是上图中的“阀门”。那么Tomcat中如何实现阀门功能以及阀门的配置呢?请移步下节

    Valve 和ValveContext

    Valve和ValveContext实际上实现了Tomcat对于阀门的控制,先看一下Valve和ValveContext的结构设计。

    Valve
    
    //Valve信息
    public String getInfo() ;
    
    //invoke
    public void invoke(Request request, Response response, ValveContext context) throws IOException;
    
    ValveContext
    
    //ValveContext信息
    public String getInfo();
    
    //向下移动
    public void invokeNext(Request request, Response response)
            throws IOException, ServletException;
    

    我们可以看到,Valve对象invoke方法中包含ValveContext。实际上ValveContext接口似于现在使用的Iterator接口。与Iterator接口不同的是,Iterator接口将集合对象的控制权集中于接口实现类内部,通过invoke内部对对象进行控制,而ValveContext是将控制权转交给Valve进行使用。并且Valve相较Iterator更为精简,只保留需要的功能。

    
    //比较简单,很好理解,结构图中有
    public interface Contained {
    
        public void setContainer(Container container);
    
        public Container getContainer();
    }
    
    **
     * StandardPipeline
     * Created by admin on 2017/8/1.
     */
    public class StandardPipeline implements Pipeline, Contained {
    
        private Container container;
    
        private Valve basicValve;
    
        private List<Valve> valves = new ArrayList<>();
    
        @Override
        public Container getContainer() {
            return this.container;
        }
    
        @Override
        public void setContainer(Container container) {
            this.container = container;
        }
    
        @Override
        public void setBasic(Valve basic) {
            this.basicValve = basic;
        }
    
        @Override
        public Valve getBasic() {
            return this.basicValve;
        }
    
        @Override
        public void addValve(Valve valve) {
            valves.add(valve);
        }
    
        @Override
        public void remove(Valve valve) {
            valves.remove(valve);
        }
    
        @Override
        public Valve[] getValves() {
            return (Valve[]) valves.toArray();
        }
    
        @Override
        public void invoke(Request request, Response response) throws IOException, ServletException {
    
            // Invoke the first Valve in this pipeline for this request
            (new StanardValveContext()).invokeNext(request, response);
        }
    
        protected class StanardValveContext implements ValveContext {
    
            protected String info = "com.cunchen.core.StandardPipeline/1.0";
    
            protected int stage = 0;
    
            @Override
            public String getInfo() {
                return info;
            }
    
            @Override
            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.size()) {
                    valves.get(subscript).invoke(request, response, this);
                } else if ((subscript == valves.size()) && (basicValve != null)) {
                    basicValve.invoke(request, response, this);
                } else {
                    throw new ServletException("standardPipeline.noValve");
                }
            }
        }
    }
    
    /**
     * 客户请求IP记录阀DEMO
     * Created by wqd on 2017/3/9.
     */
    public class ClientIPLoggerValve implements Valve, Contained {
    
        protected Container container;
    
        protected String info;
    
        private Logger log = Logger.getLogger(info);
    
        @Override
        public Container getContainer() {
            return this.container;
        }
    
        @Override
        public void setContainer(Container container) {
            this.container = container;
        }
    
        @Override
        public String getInfo() {
            return info;
        }
    
        /**
         * 代理方法
         * @param request {@link Request}
         * @param response {@link Response}
         * @param valveContext {@link ValveContext}
         * @throws IOException Valve.invoke
         * @throws ServletException Valve.invoke
         */
        @Override
        public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
    
            valveContext.invokeNext(request, response);
    
            ServletRequest request1 = request.getRequest();
            if(request1 instanceof HttpServletRequest) {
                HttpServletRequest hreq = (HttpServletRequest) request1;
                Enumeration headerNames = hreq.getHeaderNames();
                while(headerNames.hasMoreElements()) {
                    String headerName = headerNames.nextElement().toString();
    
                    String headerValue = hreq.getHeader(headerName);
    
                    log.info(getInfo() + "recorder-----" + headerName + ":"  + headerValue);
                }
            }
    
        }
    
    }
    

    从代码来看,不难理解作者其实想通过ValveContext接口,可以扩展实现不同的上下文控制类。但实际上,ValveContext这种设计最直接的造成了Valve对象的代码冗余(需要Valve.invoke执行后,调用ValveContext.nextInvoke),并且实现同样的功能完全可以通过控制集合类实现。因此在Tomcat 5以后,valveContext接口被取消,Valve接口增加setNext()方法,将Valve结构改为链表形式,有兴趣的小伙伴可以查看Tomcat 7 StandardPipeline

  • 相关阅读:
    CSS之APP开发比较实用的CSS属性
    关于 js 中的 call 和 apply使用理解
    灵感一:搜索型APP,帮助读书爱好者,搜索某本书的关键字
    常用排序算法:基数排序
    常用排序算法:桶排序
    常用排序算法:计数排序
    常用排序算法:希尔排序
    常用排序算法:归并排序
    常用排序算法:堆排序
    常用排序算法:快速排序
  • 原文地址:https://www.cnblogs.com/cunchen/p/9464110.html
Copyright © 2011-2022 走看看