Tomcat 和 Jetty 的整体架构都是基于组件的。

组件化及可配置

通过 XML 文件或者代码的方式来配置这些组件。

可以在 server.xml 配置 Tomcat 的连接器以及容器组件。
也可以在 jetty.xml 文件里组装 Jetty 的 Connector 组件,以及各种 Handler 组件。

可以灵活选择组件来搭建你的 Web 容器,并且也可以自定义组件,从而为 Web 容器提供了深度可定制化。

Web 容器如何实现组件化设计?

第一个是面向接口编程。
我们需要对系统的功能按照“高内聚、低耦合”的原则进行拆分。
每个组件都有相应的接口,组件之间通过接口通信,这样就可以方便地替换组件了。
比如我们可以选择不同连接器类型,只要这些连接器组件实现同一个接口就行。

第二个是 Web 容器提供一个载体把组件组装在一起工作。
组件的工作无非就是处理请求,因此容器通过责任链模式把请求依次交给组件去处理。

Server 概念

把组件组织起来需要一个“管理者”。这就是为什么 Tomcat 和 Jetty 都有一个 Server 的概念。
Server 就是组件的载体,Server 里包含了连接器组件和容器组件;


通过配置来组装组件

容器还需要把请求交给各个子容器组件去处理,Tomcat 和 Jetty 都是责任链模式来实现的。
用户通过配置来组装组件,跟 Spring 中 Bean 的依赖注入相似。

Spring 的用户可以通过配置文件或者注解的方式来组装 Bean。Bean 与 Bean 的依赖关系很灵活,完全由用户自己来定义。
这与Web 容器不同,Web 容器中组件与组件之间的关系是固定的,比如 Tomcat 中 Engine 组件下有 Host 组件、Host 组件下有 Context 组件。但你不能在 Host 组件里“注入”一个 Wrapper 组件,这是由 Web 容器本身的功能来决定的。

组件的创建

由于组件是可以配置的,Web 容器在启动之前并不知道要创建哪些组件。
因此不能通过硬编码的方式来实例化这些组件,需要通过反射机制来动态地创建。

具体来说,Web 容器不是通过 new 方法来实例化组件对象的,而是通过 Class.forName 来创建组件。
无论哪种方式,在实例化一个类之前,Web 容器需要把组件类加载到 JVM,这就涉及一个类加载的问题,Web 容器设计了自己类加载器。

Spring 也是通过反射机制来动态地实例化 Bean,那么它用到的类加载器是从哪里来的呢?
Web 容器给每个 Web 应用创建了一个类加载器,Spring 用到的类加载器是 Web 容器传给它的。

容器 & handler 的本质

不同类型的组件具有父子层次关系,父组件处理请求后再把请求传递给某个子组件。
无论是 Jetty 的 Handler 还是 Tomcat 的容器,本质就是为了管理一系列组件。

Jetty 中的 Handler 看似平行,实则是分层次的。
比如 WebAppContext 中包含 ServletHandler 和 SessionHandler。
也可以把 ContextHandler 和它所包含的 Handler 看作是父子关系。

Tomcat 通过容器的概念,把小容器放到大容器来实现父子关系。

组件的生命周期管理

Tomcat 和 Jetty 采用了类似的办法来管理组件的生命周期,主要有两个要点:

一是父组件负责子组件的创建、启停和销毁。
这样只要启动最上层组件,整个 Web 容器就被启动起来了,实现了一键式启停;

二是 Tomcat 和 Jetty 都定义了组件的生命周期状态,组件状态的转变定义成一个事件,一个组件的状态变化会触发子组件的变化。比如 Host 容器的启动事件里会触发 Web 应用的扫描和加载,会在 Host 容器下创建相应的 Context 容器,而 Context 组件的启动事件又会触发 Servlet 的扫描,进而创建 Wrapper 组件。

这种联动的实现手段是观察者模式。
具体来说就是创建监听器去监听容器的状态变化,在监听器的方法里去实现相应的动作,这些监听器其实是组件生命周期过程中的“扩展点”。

Spring 也采用了类似的设计,Spring 给 Bean 生命周期状态提供了很多的“扩展点”。这些扩展点被定义成一个个接口,只要你的 Bean 实现了这些接口,Spring 就会负责调用这些接口。这样做的目的就是,当 Bean 的创建、初始化和销毁这些控制权交给 Spring 后,Spring 让你有机会在 Bean 的整个生命周期中执行你的逻辑。

Spring Bean 的生命周期

image.png

组件的骨架抽象类和模板模式

具体到组件的设计的与实现,Tomcat 和 Jetty 都大量采用了骨架抽象类和模板模式。

对于 Tomcat, ProtocolHandler 接口,有抽象基类 AbstractProtocol,后者实现了协议处理层的骨架和通用逻辑。
具体协议也有抽象基类,比如 HttpProtocol 和 AjpProtocol。
对于 Jetty 来说,Handler 接口之下有 AbstractHandler,Connector 接口之下有 AbstractConnector。

抽象基类实现了一些通用逻辑,并且会定义一些抽象方法来实现骨架逻辑,而具体实现由子类完成。这是一个通用的设计规范。
不管是 Web 容器还是 Spring,甚至 JDK 本身都到处使用这种设计,比如 Java 集合中的 AbstractSet、AbstractMap 等。
值得一提的是,从 Java 8 开始允许接口有 default 方法,这样我们可以把抽象骨架类的通用逻辑放到接口中去。

组件是怎么判断这个接口被实现了,就可以被调用? isinstanceof