门面设计模式

门面设计模式(Facade Pattern)在 Tomcat 中有多处应用,例如 Request 和 Response 对象的封装、Standard Wrapper 到 ServletConfig 的封装、以及 ApplicationContext 到 ServletContext 的封装等。

门面设计模式的原理

门面设计模式的核心作用在于封装。顾名思义,就是将复杂的内部结构封装成一个统一的“门面”,以便与其他模块更便捷地进行交互,类似于国家外交部统一对外接口的角色。

该模式主要应用于由多个子系统组成的大型系统中。子系统之间必然涉及相互通信,但每个子系统不应将内部数据过多地暴露给其他系统,否则划分子系统的意义将大打折扣。因此,每个子系统都会设计一个门面,将外部感兴趣的数据封装起来,并通过这个门面提供访问接口。这就是门面设计模式存在的意义。

门面设计模式示意图如下:

图 1. 门面示意图

客户端(Client)仅能访问 Facade 提供的数据,这是门面设计模式的关键所在。至于 Client 如何访问 Facade,以及 Subsystem 如何提供 Facade,该模式并没有严格规定。

Tomcat 的门面设计模式示例

Tomcat 中包含众多不同组件,组件间需要相互交互数据,使用门面模式隔离数据是一种有效的方法。

以下是 Request 对象上使用的门面设计模式示例:

图 2. Request 的门面设计模式类图

从类图可以看出,HttpRequestFacade 类封装了 HttpRequest 接口能够提供的数据。通过 HttpRequestFacade 访问到的数据都被代理到 HttpRequest 中。通常被封装的对象会被设为 Private 或者 Protected 访问修饰符,以防止在 Facade 之外被直接访问。

观察者设计模式

观察者设计模式(Observer Pattern)是一种常用的设计方法,通常也称为发布 - 订阅模式(Publish-Subscribe Pattern)或事件监听机制。它通常在某个事件发生的前后触发一些特定操作。

观察者模式的原理

观察者模式的原理相对简单:当主体(Subject)状态发生变化时,依赖该主体的观察者(Observer)会收到通知并执行相应操作。观察者必须向主体注册(登记),否则主体无法通知它。

观察者模式通常包含以下几个角色:

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

Tomcat 的观察者模式示例

Tomcat 中多处使用了观察者模式。前面提到的控制组件生命周期的 Lifecycle 接口就是这种模式的体现,此外 Servlet 实例的创建、Session 的管理、Container 的管理等都采用了同样的原理。下面主要看一下 Lifecycle 的具体实现。

Lifecycle 的观察者模式结构图如下:

图 3. Lifecycle 的观察者模式结构图

在上述结构图中:

  • LifecycleListener 代表抽象观察者,它定义了一个 lifecycleEvent 方法,这是当主题变化时要执行的方法。
  • ServerLifecycleListener 代表具体观察者,它实现了 LifecycleListener 接口的方法。
  • Lifecycle 接口代表抽象主题,它定义了管理观察者的方法以及其他相关方法。
  • StandardServer 代表具体主题,它实现了抽象主题的所有方法。

Tomcat 对观察者模式做了扩展,增加了两个辅助类:LifecycleSupportLifecycleEvent

  • LifecycleEvent 使得可以定义事件类别,不同的事件可区别处理,更加灵活。
  • LifecycleSupport 类代理了主题对多观察者的管理,将这个管理逻辑抽出来统一实现。以后如果需要修改,只需修改 LifecycleSupport 类,而不需要修改所有具体主题,因为所有具体主题对观察者的操作都被代理给了 LifecycleSupport 类。这可以认为是观察者模式的一种改进版。

LifecycleSupport 调用观察者的方法代码如下:

public void fireLifecycleEvent(String type, Object data) {
    LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
    LifecycleListener interested[] = null;
    synchronized (listeners) {
        interested = (LifecycleListener[]) listeners.clone();
    }
    for (int i = 0; i < interested.length; i++) {
        interested[i].lifecycleEvent(event);
    }
}

主题是如何通知观察者的呢?请看以下代码示例:

public void start() throws LifecycleException {
    lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
    lifecycle.fireLifecycleEvent(START_EVENT, null);
    started = true;
    synchronized (services) {
        for (int i = 0; i < services.length; i++) {
            if (services[i] instanceof Lifecycle) {
                ((Lifecycle) services[i]).start();
            }
        }
    }
    lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}

命令设计模式

在 Tomcat 架构中,核心组件 Connector 和 Container 的交互体现了命令设计模式。Connector 将接收到的请求封装为命令,传递给 Container 进行处理。

命令模式的原理

命令模式(Command Pattern)的主要作用是将命令封装,把发出命令的责任和执行命令的责任分离开,实现功能分工。不同的模块可以对同一个命令做出不同的解释。

命令模式通常包含以下几个角色:

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

Tomcat 中的命令模式示例

Tomcat 作为应用服务器,会接收到很多请求,如何分配和执行这些请求是核心功能之一。命令模式在 Connector 和 Container 组件之间得到了体现。

以下是 Tomcat 命令模式的结构图:

图 4. Tomcat 命令模式的结构图

在此结构中:

  • Connector 作为抽象请求者,HttpConnector 作为具体请求者。
  • HttpProcessor 作为命令。
  • Container 作为命令的抽象接受者,ContainerBase 作为具体的接受者。
  • 客户端即应用服务器 Server 组件。

Server 首先创建命令请求者 HttpConnector 对象,然后创建命令 HttpProcessor 对象,再把命令对象交给命令接受者 ContainerBase 容器来处理。命令最终是被 Tomcat 的 Container 执行的。命令可以以队列的方式进入,Container 也可以以不同的方式来处理请求,例如 HTTP/1.0 协议和 HTTP/1.1 的处理方式会有所不同。

责任链模式

责任链模式(Chain of Responsibility Pattern)是 Tomcat 中最容易发现的设计模式之一,也是 Tomcat 中 Container 设计的基础。整个容器通过一条链连接在一起,这条链一直将请求正确地传递给最终处理请求的 Servlet。

责任链模式的原理

责任链模式将很多对象通过每个对象对其下家的引用连接起来形成一条链。请求在这条链上传递,直到链上的某个对象处理此请求;或者每个对象都可以处理请求,并传给下一家,直到最终链上每个对象都处理完。这样可以在不影响客户端的情况下,在链上增加任意的处理节点。

通常责任链模式包含以下几个角色:

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

Tomcat 中责任链模式示例

在 Tomcat 中,这种设计模式几乎被完整地使用。Tomcat 的容器设置就是责任链模式,从 Engine 到 Host,再到 Context,一直到 Wrapper,都是通过一个链传递请求。

Tomcat 中责任链模式的类结构图如下:

图 5. Tomcat 责任链模式的结构图

上图基本描述了四个子容器使用责任链模式的类结构。对应的责任链模式角色中,Container 扮演抽象处理者角色,具体处理者由 StandardEngine 等子容器扮演。

与标准的责任链不同的是,这里引入了 PipelineValve 接口。它们有什么作用呢?

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

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


说明:本文涉及的部分类名(如 HttpConnectorHttpProcessor)主要基于 Tomcat 5/6 版本架构。在 Tomcat 7 及更高版本中,Connector 和 Container 的内部实现有所调整(例如移除了 HttpConnector),但整体设计模式思想(门面、观察者、命令、责任链)依然适用。