Tomcat涉及的设计模式

一、门面设计模式

门面设计模式在 Tomcat 中有多处使用,在 Request 和 Response 对象封装中、Standard Wrapper 到 ServletConfig 封装中、ApplicationContext 到 ServletContext 封装中等都用到了这种设计模式。

1.1 原理

这么多场合都用到了这种设计模式,那这种设计模式究竟能有什么作用呢?顾名思义,就是将一个东西封装成一个门面好与人家更容易进行交流,就像一个国家的外交部一样。

这种设计模式主要用在一个大的系统中有多个子系统组成时,这多个子系统肯定要涉及到相互通信,但是每个子系统又不能将自己的内部数据过多的暴露给其它系统,不然就没有必要划分子系统了。每个子系统都会设计一个门面,把别的系统感兴趣的数据封装起来,通过这个门面来进行访问。这就是门面设计模式存在的意义。
图 1. 门面示意图
Client 只能访问到 Façade 中提供的数据是门面设计模式的关键,至于 Client 如何访问 FaçadeSubsystem 如何提供 Façade 门面设计模式并没有规定死。

1.2 Tomcat中的实例

Tomcat使用外观设计模式主要是为了保证主要类的安全,防止程序员使用核心类的不需要暴露出去的功能。

Tomcat 中门面设计模式使用的很多,因为 Tomcat 中有很多不同组件,每个组件要相互交互数据,用门面模式隔离数据是个很好的方法。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • RequestFacade作为Request的门面,内部包含Request对象;
  • ResponseFacade作为Response的门面,内部包含Response对象;
  • ApplicationContextFacade作为ApplicationContext的门面,内部包含ApplicaitonContext对象;

1.2.1 代码实例

public class RequestFacade implements HttpServletRequest {

    public RequestFacade(Request request) {
        this.request = request;
    }

    protected Request request = null;
    public Object getAttribute(String name) {
        if (request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        }
        return request.getAttribute(name);
    }

    public Enumeration<String> getAttributeNames() {
        if (request == null) {
            throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
        }
        if (Globals.IS_SECURITY_ENABLED){
            return AccessController.doPrivileged(
                new GetAttributePrivilegedAction());
        } else {
            return request.getAttributeNames();
        }
    }
}
public class Request implements org.apache.catalina.servlet4preview.http.HttpServletRequest {
    private HttpServletRequest applicationRequest = null;
    protected RequestFacade facade = null;
    public HttpServletRequest getRequest() {
        if (facade == null) {
            facade = new RequestFacade(this);
        }
        if (applicationRequest == null) {
            applicationRequest = facade;
        }
        return applicationRequest;
    }
}

二、责任链设计模式

Tomcat中的Filter就是使用了责任链模式,创建一个Filter除了要在web.xml文件中做相应配置外,还需要实现javax.servlet.Filter接口。也是 Tomcat 中 Container 设计的基础,整个容器的就是通过一个链连接在一起,这个链一直将请求正确的传递给最终处理请求的那个 Servlet。在 tomcat 中这种设计模式几乎被完整的使用,tomcat 的容器设置就是责任链模式,从 Engine 到 Host 再到 Context 一直到 Wrapper 都是通过一个链传递请求。

2.1 原理

责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。

  • Handler(抽象处理者):定义一个处理请求的接口
  • ConcreteHandler(具体处理者):处理请求的具体类,或者传给下家

2.2 Tomcat中的实例

在这里插入图片描述
上图基本描述了四个子容器使用责任链模式的类结构图,对应的责任链模式的角色,Container扮演抽象处理者角色,具体处理者由 StandardEngine 等子容器扮演。与标准的责任链不同的是,这里引入了 PipelineValve 接口。他们有什么作用呢?

实际上 PipelineValve 是扩展了这个链的功能,使得在链往下传递过程中,能够接受外界的干预。Pipeline 就是连接每个子容器的管子,里面传递的 RequestResponse 对象好比管子里流的水,而 Valve就是这个管子上开的一个个小口子,让你有机会能够接触到里面的水,做一些额外的事情。

为了防止水被引出来而不能流到下一个容器中,每一段管子最后总有一个节点保证它一定能流到下一个子容器,所以每个容器都有一个 StandardXXXValve。只要涉及到这种有链式是处理流程这是一个非常值得借鉴的模式。

从上图中,我们可以看到每一个容器都会有一个Pipeline,而一个Pipeline又会具有多个Valve阀门,其中StandardEngine对应的阀门是StandardEngineValveStandardHost对应的阀门是StandardHostValveStandardContext对应的阀门是StandardContextValveStandardWrapper对应的阀门是StandardWrapperValve。这里每个Pipeline就好比一个管道,而每一Valve就相当于一个阀门,一个管道可以有多个阀门,而对于阀门来说有两种,一种阀门在处理完自己的事情以后,只需要将工作委托给下一个和自己在同一管道的阀门即可,第二种阀门是负责衔接各个管道的,它负责将请求传递给下个管道的第一个阀门处理,而这种阀门叫Basic阀门,它是每个管道中最后一个阀门,上面的Standard*Valve都属于第二种阀门。我们可以形象的通过下图来描述上面的过程:
在这里插入图片描述
通过上图,我们可以很清楚的了解到Tomcat的请求处理流程。
当用户请求服务器的时候,Connector会接受请求,从Socket连接中根据http协议解析出对应的数据,构造Request和Response对象,然后传递给后面的容器处理,顶层容器是StandardEngine,StandardEngine处理请求其实是通过容器的Pipeline进行的,而Pipeline其实最终是通过管道上的各个阀门进行的,当请求到达StandardEngineValve的时候,此阀门会将请求转发给对应StandardHost的Pipeline的第一个阀门处理,然后以此最终到达StandardHostValve阀门,它又会将请求转发给StandardContext的Pipeline的第一个阀门,这样以此类推,最后到达StandardWrapperValve,此阀门会根据Request来构建对应的Servelt,并将请求转发给对应的HttpServlet处理。从这里我们可以看出其实Tomcat核心处理流程就是通过责任链一步步的组装起来的。

三、命令设计模式

命令模式将请求封装为一个命令,将命令发送者和命令接受者解耦,并且所有命令对客户端来说都有统一的调用接口,使用命令模式还可以支持命令的撤销操作,在很多GUI程序中大量使用了此模式。

3.1 原理

命令模式主要作用就是封装命令,把发出命令的责任和执行命令的责任分开。也是一种功能的分工。不同的模块可以对同一个命令做出不同解释。
下面是命令模式通常包含下面几个角色:

  • Client:创建一个命令,并决定接受者
  • Command 命令:命令接口定义一个抽象方法
  • ConcreteCommand:具体命令,负责调用接受者的相应操作
  • Invoker 请求者:负责调用命令对象执行请求
  • Receiver 接受者:负责具体实施和执行一次请求

接下来我们来说一个场景大家感受下,我们有时候可能会遇到接口方法参数过多的问题,这样的接口不仅看起来丑陋而且不方便阅读,对客户端不友好。遇到这种情况我们可能选择将各种参数打包为一个参数对象,接口只需要一个参数对象即可,但是在具体的接口实现中,我们又要做条件判断根据参数值的不同做出不同的响应操作,这个时候其实就可以考虑将不同的逻辑实现和各种参数通过命令打包,然后提供一个命令工厂,客户端通过工厂生产出命令,然后直接调用即可。
其实在日常生活中,命令模式也很常见,比如公司老大给你分配了个任务,让你去做,他可能不关心你具体怎么做的,你做完了以后告诉他结果即可。

3.2 代码

接口

//接口
public interface Command {
	void execute();
}

实现类

public class ConcreteCommandA implements ICommand {
    private Receiver receiver;
    public ConcreteCommandA(Receiver receiver){
        this.receiver = receiver;
    }
    @Override
    public void execute() {
        System.out.println("ConcreteCommandA execute ...");
        receiver.execute();
    }
}
public class ConcreteCommandB implements ICommand {
    private Receiver receiver;
    public ConcreteCommandB(Receiver receiver){
        this.receiver = receiver;
    }
    @Override
    public void execute() {
        System.out.println("ConcreteCommandB execute ...");
        receiver.execute();
    }
}

执行类

/**
 * 命令执行对象
 */
public class Receiver {
    public void execute(){
        System.out.println("receiver execute ... ");
    }
}

执行入口

public class Invoker {
    private Command concreteCommandA, concreteCommandB;
    public Invoker(Command concreteCommandA, Command concreteCommandB){
        this.concreteCommandA = concreteCommandA;
        this.concreteCommandB = concreteCommandB;
    }
    public void orderA(){
        concreteCommandA.execute();
    }
    public void orderB(){
        concreteCommandB.execute();
    }
}

调用

public static void main(String[] args) {
    Receiver receiver = new Receiver();
    Invoker invoker = new Invoker(new ConcreteCommandA(receiver), new ConcreteCommandB(receiver));
    invoker.orderA();
    invoker.orderB();
}

3.3 Tomcat实例

命令模式在Tomcat中主要是应用在对请求的处理过程中,Tomcat根据它支持两种协议AJP和Http,而在具体的IO实现中,又分为Java同步阻赛IO,Java同步非阻塞IO,以及采用APRApache Portable Runtime 支持库,因此Tomcat统一了org.apache.coyote.Processor接口,根据协议和IO实现的不同,使用不同的Process子类去实现,Connector作为客户端每次只需要根据具体的协议和IO实现创建对应的Process执行即可。下面我们来看一下命令模式在Tomcat中实现的相关类图:
在这里插入图片描述
通过上图我们可以清楚的看到,Tomcat首先根据协议的不同将Processor分为了Ajp和Http两组,然后又根据具体的IO实现方式的不同,将每一组都会实现同步祖塞IO,同步非祖塞IO,以及APR的Processor。

针对每一种协议和IO实现方式的组合,都会有相应的协议处理类,而每个协议处理类都会有一个Handler,而每一个Handler在运行的时候就会创建出对应的Processor,比如AjpProtocol.AjpConnectionHandler创建AjpProcessor处理器,其它的类似。
通过上面的描述,我们可以看出Tomcat接受请求的处理流程如下:

Connector通过对应的Endpint监听Socket连接,当对应的端口有连接进来的时候,对应的Endpoint就会通过对应的Handler类处理,而Handler处理的时候,又会创建对应的Processor处理,而对应的Processor命令对象会解析Socket流的数据,然后生成Request和Response对象,最终通过上面说的责任链模式一步步的通过各个容器。

四、观察者设计模式

这种设计模式也是常用的设计方法通常也叫发布 - 订阅模式,也就是事件监听机制,通常在某个事件发生的前后会触发一些操作。有多个对象在关注着一个对象,如果这个对象的状态发生了改变,其它依赖(关注)它的对象就会收到通知,然后在接收到通知以后各个对象做出相应的动作。
观察者模式涉及到两个概念(观察者和被观察者),被观察者只能有一个,而观察这个观察者的对象可以用多个。【一对多】定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

4.1 原理

观察者模式原理也很简单,就是你在做事的时候旁边总有一个人在盯着你,当你做的事情是它感兴趣的时候,它就会跟着做另外一些事情。但是盯着你的人必须要到你那去登记,不然你无法通知它。观察者模式通常包含下面这几个角色:

  • Subject 就是抽象主题:它负责管理所有观察者的引用,同时定义主要的事件操作。
  • ConcreteSubject 具体主题:它实现了抽象主题的所有定义的接口,当自己发生变化时,会通知所有观察者。
  • Observer 观察者:监听主题发生变化相应的操作接口。

4.2 代码

4.2.1 被观察者

  1. 被观察者对象维护一个观察者的列表对象和注册观察者的接口。
  2. 事件发生变化时候遍历所有观察者列表并触发事件。
  3. 观察者统一实现某接口用以事件通知。
/***
 * 抽象被观察者接口
 * 声明了添加、删除、通知观察者方法
 *
 */
public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObserver();
    
}
/**
 * 被观察者,也就是微信公众号服务
 * 实现了Observerable接口,对Observerable接口的三个方法进行了具体实现
 *
 */
public class ConcreteSubject implements Subject {
    
    //注意到这个List集合的泛型参数为Observer接口,设计原则:面向接口编程而不是面向实现编程
    private List<Observer> list;
    private String message;
    
    public WechatServer() {
        list = new ArrayList<Observer>();
    }
    
    @Override
    public void registerObserver(Observer o) {
        
        list.add(o);
    }
    
    @Override
    public void removeObserver(Observer o) {
        if(!list.isEmpty())
            list.remove(o);
    }

    //遍历
    @Override
    public void notifyObserver() {
        for(int i = 0; i < list.size(); i++) {
            Observer oserver = list.get(i);
            oserver.update(message);
        }
    }
    
    public void setInfomation(String s) {
        this.message = s;
        System.out.println("微信服务更新消息: " + s);
        //消息更新,通知所有观察者
        notifyObserver();
    }

}

4.2.2 观察者

  1. 观察者需要定义统一的接口用以处理事件通知。
/***
 * 抽象观察者
 * 定义了一个update()方法,当被观察者调用notifyObservers()方法时,观察者的update()方法会被回调。
 *
 */
public interface Observer {
    public void update(String message);
}

/**
 * 观察者
 * 实现了update方法
 */
public class User implements Observer {

    private String name;
    private String message;
    
    public User(String name) {
        this.name = name;
    }
    
    @Override
    public void update(String message) {
        this.message = message;
        read();
    }
    
    public void read() {
        System.out.println(name + " 收到推送消息: " + message);
    }
}

4.2.3 测试验证

public class Test {
    public static void main(String[] args) {
        WechatServer server = new WechatServer();
        
        Observer mingjing = new User("MingJing");
        Observer allen = new User("Allen");
        Observer huosen = new User("HuoSen");
        
        server.registerObserver(mingjing);
        server.registerObserver(allen);
        server.registerObserver(huosen);
        server.setInfomation("中午十二点吃饭!");
        
        System.out.println("----------------------------------------------");
        server.removeObserver(Allen);
        server.setInfomation("我开会,可能到十二点半才去吃中午饭!");  
    }
}

4.3 Tomcat实例

4.3.1 观察者

  1. 观察者实现统一的接口LifecycleListener,实现具体的方法lifecycleEvent。
  2. 观察者的事件对象继承了EventObject类,这个有兴趣可以研究下。
  3. 观察者的具体实现以HostConfig为例,实现了具体的lifecycleEvent方法。
//1
public final class LifecycleEvent extends EventObject {
    private static final long serialVersionUID = 1L;

    public LifecycleEvent(Lifecycle lifecycle, String type, Object data) {
        super(lifecycle);
        this.type = type;
        this.data = data;
    }

    private final Object data;
    private final String type;

    public Object getData() {
        return data;
    }

    public Lifecycle getLifecycle() {
        return (Lifecycle) getSource();
    }

    public String getType() {
        return this.type;
    }
}
//2
public interface LifecycleListener {
    public void lifecycleEvent(LifecycleEvent event);
}
//3
public class HostConfig implements LifecycleListener {
    public void lifecycleEvent(LifecycleEvent event) {
        try {
            host = (Host) event.getLifecycle();
            if (host instanceof StandardHost) {
                setCopyXML(((StandardHost) host).isCopyXML());
                setDeployXML(((StandardHost) host).isDeployXML());
                setUnpackWARs(((StandardHost) host).isUnpackWARs());
                setContextClass(((StandardHost) host).getContextClass());
            }
        } catch (ClassCastException e) {
            log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
            return;
        }

        if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
            check();
        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            beforeStart();
        } else if (event.getType().equals(Lifecycle.START_EVENT)) {
            start();
        } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
            stop();
        }
    }
}

4.3.2 被观察者

  1. 被观察者实现接口Lifecycle,实现addLifecycleListenerremoveLifecycleListener方法。
  2. 容器的基类LifecycleBase实现了被观察者功能,提供List<LifecycleListener> lifecycleListeners保存被观察者。
  3. 容器的具体实现当中都是继承LifecycleBase类,所以天然包含了被观察者的功能。
public interface Lifecycle {
    public void addLifecycleListener(LifecycleListener listener);
    public LifecycleListener[] findLifecycleListeners();
    public void removeLifecycleListener(LifecycleListener listener);
}

public abstract class LifecycleBase implements Lifecycle {
    private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>();
    private volatile LifecycleState state = LifecycleState.NEW;

    public void addLifecycleListener(LifecycleListener listener) {
        lifecycleListeners.add(listener);
    }

    public LifecycleListener[] findLifecycleListeners() {
        return lifecycleListeners.toArray(new LifecycleListener[0]);
    }

    public void removeLifecycleListener(LifecycleListener listener) {
        lifecycleListeners.remove(listener);
    }

    protected void fireLifecycleEvent(String type, Object data) {
        LifecycleEvent event = new LifecycleEvent(this, type, data);
        for (LifecycleListener listener : lifecycleListeners) {
            listener.lifecycleEvent(event);
        }
    }
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Tomcat的启动流程可以通过对源码的分析来理解。在Tomcat,整个启动和关闭过程是按照一定规则进行的,通过Lifecycle接口来管理组件的生命周期。\[2\]在启动过程,父组件会调用子组件的init()和start()方法。因此,只要调用最顶层组件也就是Server组件的init()和start()方法,整个Tomcat就会被启动起来。\[2\] 在启动过程涉及到的组件需要进行初步梳理,并结合server.xml的标签来理解各个组件的作用。可以通过Tomcat的架构设计图来进行梳理和修正。\[1\]在启动过程,还涉及到了模板方法的设计模式,其抽象方法会被具体的子类实现。这个过程可能会有一些绕,但是通过对源码的调试和理解,可以逐步搞清楚整个启动流程。\[1\] 此外,由于Tomcat定义了自己的Request类来存放客户端发来的请求信息,而这个Request对象不是标准的ServletRequest。为了解决这个问题,Tomcat引入了CoyoteAdapter,通过适配器模式将Tomcat Request转换成ServletRequest,再调用容器的Service方法。\[3\] 总结起来,Tomcat的启动流程可以通过对源码的分析和调试来理解,其涉及到组件的初始化和启动过程,以及对请求信息的处理和适配。 #### 引用[.reference_title] - *1* [Tomcat-启动流程](https://blog.csdn.net/wyy546792341/article/details/126576803)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Tomcat主要结构和启动流程](https://blog.csdn.net/songcf_faith/article/details/124653240)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值