Tomcat架构设计分析初体验

一、简单介绍:

目录

一、简单介绍:

二、核心架构:

2.1 组件详解

Server组件

Service组件

Connector组件

Container组件

2.2 请求定位Servlet过程

三、Tomcat设计架构精髓分析(重点)

3.1 Connector高内聚低耦合设计

ProtocolHandler(Endpoint+Processor)

1.EndPoint

2.Processor     

3.Adapter

适配器模式:

3.2 Container父子容器架构设计

1.父子容器组合模式设计

2.Pipeline-Valve责任链模式设计

3.3 Tomcat生命周期设计

组合模式

观察者模式

模板设计模式

四、总结


        众所周知,Tomcat是一款常用的web服务器,它是由Apache Software Foundation开发的一款开源的Java Servlet容器。它可以为Java Web应用程序提供运行的环境,并通过HTTP协议处理客户端请求。它主要负责监听和接收前端请求,并生成对应的Servlet,传递给后端进行相应的数据请求和处理,最后再将后端结果返回给前端。

        打个通俗易懂的比方,它就像一个饭馆里的传菜员,记录下客人点的菜后,报告给后厨,后厨在后台做好菜后,传菜员将菜盘端到前端客桌上;同时还要随时盯着,如果又有新的客人到来,要及时招呼入座,并提供服务。

二、核心架构:

Tomcat核心:HTTP服务器+Servlet容器

Tomcat要实现2个核心功能:

  • 处理Socket连接,负责网络字节流Request和Response对象的转化。
  • 加载和管理Servlet,以及具体处理Request请求。

        因此,Tomcat设计了两个核心组件,连接器(Connector)容器(Container)来分别做这两件事。继续打个比方,有个外交大臣名叫Connector(连接器)负责对外交流,再设一个丞相名叫Container(容器)负责处理内部事务。

2.1 组件详解

Server组件

        指的就是整个Tomcat服务器,包含多组服务(Service),负责管理和启动各个Service,同时监听8005端口发过来的shutdown命令,用于关闭整个容器。

Service组件

        每个Service组件都包含了若干用于接收客户端消息的Connector组件和处理请求的Engine组件。Service组件还包含了若干Excutor组件(线程池),它可以为Service内所有组件提供线程池执行任务。Tomcat内可以配置多个Service,这样可以实现通过不同的端口号来访问同一台机器上部署的不同应用。

Connector组件

        Tomcat与外部世界的连接器,监听固定端口接收外部请求,传递给Container,并将Container处理结果返回给外部。连接器对Servlet容器屏蔽了不同的应用层协议及I/O模型,无论是HTTP还是AJP,再容器中获取到的都是一个标准的ServletRequest对象。

(AJP, Apache JServ Protocol, 与HTTP文本传输协议不同,它是二进制传输协议,更高效。)

Container组件

        容器就是用来装载Servlet的,Tomcat设计了一个分层结构,父子层级关系的4种容器,分别是Engine、Host、Context、Wrapper。

  • Engine: 引擎,Servlet的顶层容器,用来管理多个虚拟站点,一个Service最多只能有一个Engine;
  • Host: 虚拟主机,负责web应用的部署和Context的创建。可以给Tomcat配置多个虚拟主机地址,而迅即主机下可以部署多个Web应用程序;
  • Context: Web应用上下文,包含多个Wrapper,负责web配置的解析,管理所有的Web资源,一个Context对应一个Web应用;
  • Wrapper: 表示一个Servlet,最底层的容器,是对Servlet的封装,负责Servlet实例的创建、执行和销毁。

2.2 请求定位Servlet过程

        Tomcat用Mapper组件来完成这个任务的。Mapper组件的功能就是将用户请求的URL定位到一个Servlet,它的工作原理是:Mapper组件里保存了Web应用的配置信息,其实就是容器组件与访问路径的映射关系,比如Host容器里配置的域名、Context容器里的Web应用路径,以及Wrapper容器里Servlet的映射的路径,你可以想象这些配置信息就是一个多层次的Map。当请求到来时,Mapper组件通过解析请求URL里的域名和路径,再到自己保存的Map里去查找,就能定位到一个Servlet。一个请求URL最后只会定位到一个Wrapper容器,也就是一个Servlet。

三、Tomcat设计架构精髓分析(重点)

3.1 Connector高内聚低耦合设计

Connector需要实现的功能:

  • 监听网络端口
  • 接受网络连接请求
  • 读取请求网络字节流
  • 根据具体应用层协议(HTTP/AJP)解析字节流,生成统一的Tomcat Request对象
  • 将Tomcat Request对象转成标准的ServletRequest
  • 调用Servlet容器,得到ServletResponse
  • 将ServletResponse对象转成Tomcat Response对象
  • 将Tomcat Response转成网络字节流
  • 将响应字节流写回给浏览器

分析连接器详细功能列表,我们会发现连接器需要完成3个高内聚的功能:

  • 网络通信。
  • 应用层协议解析。
  • Tomcat Request/Response 与 ServletRequest/ServletResponse 的转化。

因此Tomcat的设计者设计了3个组件来实现这3个功能,分别是EndPoint、Processor和Adapter。

  • EndPoint负责提供字节流给Processor;
  • Processor负责提供Tomcat Request对象给Adapter;
  • Adapter负责提供ServletRequest对象给容器。
ProtocolHandler(Endpoint+Processor)

        由于I/O模型和应用层协议可以自由组合,比如NIO+HTTP或者NIO2+AJP。Tomcat的设计者将网络通信和应用层协议解析放在一起考虑,设计了一个叫ProtocolHandler的接口来封装这两种变化点。各种协议和通信模型的组合有相应的具体实现类。比如:Http11NioProtocol和AjpNioProtocol。

        连接器用ProtocolHandler来处理网络通信和应用层协议,包含2个重要的部件:Endpoint和Processor。

        连接器用ProtocolHandler接口来封装通信协议和I/O模型的差异,ProtocolHandler内部又分为EndPoint和Processor模块,EndPoint负责底层Socket通信,Processor负责应用层协议解析,连接器通过适配器Adpter调用容器。

1.EndPoint

        EndPoint是通信端点,即通信监听的接口,是具体的Socket接收和发送处理器,是对传输层的抽象,因此EndPoint是用来实现TCP/IP协议的。

        它的具体子类,比如NioEndpoint和Nio2Endpoint中,有两个重要的子组件:Acceptor和SocketProcessor。其中Acceptor用于监听Socket连接请求。SocketProcessor用于处理接收到的Socket请求,它实现Runnable接口,在Run方法里调用协议处理组件Processor进行处理。为了提高处理能力,SocketProcessor被提交到线程池来执行,而这个线程池叫做执行器(Excutor)

        打个比方,EndPoint相当于情报站,其中的Acceptor是带着耳机时刻在监听的监听员,SocketProcessor是情报传送员,是个跑腿的,负责把获得的情报送到Processor,破解密码的部门,Excutor是交通部门,有多辆公家车,送情报时可以开车去。

2.Processor     

        Processor用来实现HTTP/AJP协议,Processor接收来自EndPoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理,具体的实现有AJPProcessor、HTTP11Processor等,这些具体实现类实现了特定协议的解析方法和请求处理方式。Processor是对应用层协议的抽象。

        打个比方,Processor就是情报破译处理部门,把监听到的加密情报统一处理成Tomcat标准的信息,Tomcat Request和Response。

3.Adapter

        由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat定义了自己的Request类来“存放”这些请求信息。ProtocolHandler接口负责解析请求并生成Tomcat Request类。但是这个Request对象不是标准的ServletRequest,也就意味着,不能用Tomcat Request作为参数来调用容器。Tomcat设计者的解决方案是引入CoyoteAdapter,这是适配器模式的经典运用,连接器调用CoyoteAdapter的Service方法,传入的是Tomcat Request对象,CoyoteAdapter负责将Tomcat Request转成ServletRequest,再调用容器的Service方法。

        Adapter就是个适配器,将CoyoteAdapter负责将Tomcat Request转成ServletRequest,就像手机充电器,将家用220V电压转成手机需要的4V。

适配器模式:


        我们需要调用connector对象的service方法,该方法需要传参ServletRequest和ServletResponse,目前我们只有Tomcat Request和Tomcat Response。用适配器模式的解法是创建一个CoyoteAdapter类继承Adapter,有参构造传参一个Connector。再写一个service方法,传参Tomcat Request和Tomcat Response,在该方法中将Tomcat Request和Tomcat Response转换成ServletRequest和ServletResponse,接下来就可以调用刚刚传参进来的connector对象的service方法就行了:

connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

3.2 Container父子容器架构设计

1.父子容器组合模式设计

        Tomcat采用组合模式来管理这些容器。具体实现方法是,所有容器组件都实现了Container接口,因此组合模式可以使得用户对单容器对象和组合容器对象的使用具有一致性。

        Container接口定义如下:

public interface Container extends Lifecycle {
 
    public void setName(String name);
 
    public String getName();
 
    public Container getParent();
 
    public void setParent(Container container);
 
    public void addChild(Container child);
 
    public Container findChild(String name);
 
    public void removeChild(Container child);
 
    public Container[] findChildren();
 
    public void backgroundProcess();
 
    public void invoke(Request request, Response response) throws IOException, ServletException;
 
    // 其他方法...
}
2.Pipeline-Valve责任链模式设计

        连接器中的Adapter会调用容器的Service方法来执行Servlet,最先拿到请求的是Engine容器,Engine容器对请求做一些处理后,会把请求传给自己的子容器Host继续处理,以此类推,最后这个请求会传给Wrapper容器,Wrapper容器会调用最终的Servlet来处理。

        该过程靠Pipeline-Valve管道机制实现,这是一种责任链模式。责任链模式是指再一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者做自己相应的处理,处理完之后将再调用下一个处理者继续处理。这样做可以方便扩展,用一条管道把若干阀门对象连接起来,而处理逻辑放在阀门上,管道可以任意增删相应的阀门,大大提高了灵活性和复用性。

        Valve表示一个处理点,比如权限认证和记录日志。

public interface Valve {

        public Valve getNext();

        public void setNext(Valve valve);

        public void invoke(Request request, Response response) throws IOException, ServletException;

}

       由于Pipeline是为容器设计的,所以它在设计时加入了一个Contained接口,就是为了制定当前Pipeline所属的容器

public interface Pipeline {
    // 添加Valve到Pipeline
    public void addValve(Valve valve);
 
    // 获取Pipeline中的所有Valve
    public Valve[] getValves();
 
    // 获取Pipeline关联的Contained对象
    public Contained getContainer();
 
    // 设置Pipeline关联的Contained对象
    public void setContainer(Contained container);
 
    // 获取第一个Valve
    public Valve getFirstValve();
 
    // 获取Pipeline的基础请求处理者
    public BasicValve getBasic();
 
    // 调用Pipeline中的Valve进行请求处理
    public void invoke(Request request, Response response) throws IOException, ServletException;
}

         Pipeline中维护了Valve链表,Valve可以插入到Pipeline中,对请求做某些处理。整个调用链的触发是Valve来完成的,Valve完成自己的处理后,调用getNext.invoke()来触发下一个Valve调用。每一个容器都有一个Pipeline对象,只要触发这个Pipeline的第一个Valve,这个容器里的Pipeline中的Valve都会被调用到。Basic Valve处于Valve链表的末端,它是Pipeline中必不可少的一个Valve,负责调用下一层容器的Pipeline里的第一个Valve。

        整个调用过程由连接器中的Adapter触发,它会调用Engine的第一个Valve:

connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

        Wrapper容器的最后一个Valve会创建一个Filter链,并调用doFilter()方法,最终会调用到Servlet的service方法。

filterChain.doFilter(request.getRequest(), response.getResponse());

Valve和Filter的区别:

  • Valve是Tomcat的私有机制,与Tomcat的基础架构/API是紧耦合的。Servlet API是共有的标准,所有Web容器包括Jetty都支持Filter机制。
  • Valve工作在Web容器级别,拦截所有应用的请求;而Servlet Filter工作在应用级别,只能拦截某个web应用的所有请求。

3.3 Tomcat生命周期设计

组合模式

        Tomcat有众多组件,在做系统设计时就要找到系统的变化点和不变点。这里的不变点就是每个组件都要经历创建、初始化、启动、停止这几个过程,这些状态以及状态的转化是不变的。而变化点是每个具体组件的初始化方法,也就是启动方法是不一样的。因此,我们把不变点抽象出来成为一个接口,这个接口跟生命周期有关,叫作LifeCycle。LifeCycle接口里应该定义这么几个方法:init()、start()、stop()和destroy(),每个具体的组件去实现这些方法。

public interface Lifecycle {
    // 启动组件
    public void start() throws LifecycleException;
 
    // 停止组件
    public void stop() throws LifecycleException;
 
    // 暂停组件
    public void pause() throws LifecycleException;
 
    // 恢复组件
    public void resume() throws LifecycleException;
 
    // 通知组件进入full方式
    public void destroy() throws LifecycleException;
 
    // 获取组件的状态
    public LifecycleState getState();
 
    // 添加监听器
    public void addLifecycleListener(LifecycleListener listener);
 
    // 移除监听器
    public void removeLifecycleListener(LifecycleListener listener);
}

        在父组件的init()方法里需要创建子组件并调用子组件的init()方法。同样,在父组件的start()方法里也需要创建子组件并调用子组件的start()方法,因此调用者可以通过调用最顶层组件,也就是Server组件的init()和start()方法,从而无差别调用各组件的init()和start()方法,从而启动整个Tomcat,这就是组合模式的运用。

观察者模式

        因为各个组件init()和start()方法的具体实现是复杂多变的,比如在Host容器的启动方法里需要扫描webapps目录下的Web应用,创建相应的Context容器,如果将来需要增加新的逻辑,直接修改start()方法?这样会违反开闭原则,那如何解决这个问题呢?

        组件的init()和start()调用是由它的父组件的状态变化触发的,上层组件的初始化会触发子组件的初始化,上层组件的启动会触发子组件的启动,因此我们把组件的生命周期定义成一个个状态,把状态的转变看作是一个事件。而事件是有监听器的,在监听器里可以实现一些逻辑,并且监听器也可以方便地添加和删除,在父组件创建子组件时,就会将监听器注册到子组件中去。这就是典型的观察者模式

模板设计模式

        Tomcat定义了一个基类LifeCycleBase来实现LifeCycle接口,把一些公共的逻辑放到基类中去,比如生命状态的转变与维护、生命周期事件的触发以及监听器的添加和删除等,而子类就负责实现自己的初始化、启动和停止等方法。        

        LifeCycleBase实现类LifeCycle接口中所有的方法,还定义了相应的抽象方法交给具体子类去实现,这是典型的模板设计模式(骨架抽象类和模板方法)。

        LifeCycleBase的init()方法实现:

public final synchronized void init() throws LifecycleException {
    // 状态检查
    if (!state. Equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT, state);
    }
    // 触发INITIALIZING事件的监听器
    setStateInternal(LifecycleState.INITIALIZING, null, false);
 
    try {
        // 调用具体子类的初始化方法
        initInternal();
        // 触发INITIALIZED事件的监听器
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    } catch (Throwable t) {
        handleSubClassException(t, "lifecycleBase.initFail", toString());
    }
}
 
protected abstract void initInternal() throws LifecycleException;

四、总结

         Tomcat为了实现一键式启停以及优雅的生命周期管理,并考虑了可扩展性和可重用性,将面向对象思想和设计模式发挥到了极致,分别运用了组合模式、观察者模式、骨架抽象类和模板方法。如果你需要维护一堆具有父子关系的实体,可以考虑使用组合模式。观察者模式听起来高大上,其实就是当一个事件发生后,需要执行一连串更新操作。传统的实现是在事件响应代码里直接加更新逻辑,当更新逻辑加多了之后,代码会变得臃肿,并且这种方式时紧耦合的、侵入式的。观察者模式实现了低耦合、非侵入式的通知与更新机制。模板方法在抽象基类中经常用到,用来实现通用逻辑。

  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值