<audio id="audio" title="11 | 总结：从Tomcat和Jetty中提炼组件化设计规范" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6e/42/6ea42a9860fc77edb204200e5fd49742.mp3"></audio>

在当今的互联网时代，我们每个人获取信息的机会基本上都是平等的，但是为什么有些人对信息理解得更深，并且有自己独到的见解呢？我认为是因为他们养成了思考和总结的好习惯。当我们学习一门技术的时候，如果可以勤于思考、善于总结，可以帮助我们看到现象背后更本质的东西，让我们在成长之路上更快“脱颖而出”。

我们经常谈敏捷、快速迭代和重构，这些都是为了应对需求的快速变化，也因此我们在开始设计一个系统时就要考虑可扩展性。那究竟该怎样设计才能适应变化呢？或者要设计成什么样后面才能以最小的成本进行重构呢？今天我来总结一些Tomcat和Jetty组件化的设计思想，或许从中我们可以得到一些启发。

## 组件化及可配置

Tomcat和Jetty的整体架构都是基于组件的，你可以通过XML文件或者代码的方式来配置这些组件，比如我们可以在server.xml配置Tomcat的连接器以及容器组件。相应的，你也可以在jetty.xml文件里组装Jetty的Connector组件，以及各种Handler组件。也就是说，**Tomcat和Jetty提供了一堆积木，怎么搭建这些积木由你来决定**，你可以根据自己的需要灵活选择组件来搭建你的Web容器，并且也可以自定义组件，这样的设计为Web容器提供了深度可定制化。

那Web容器如何实现这种组件化设计呢？我认为有两个要点：

- 第一个是面向接口编程。我们需要对系统的功能按照“高内聚、低耦合”的原则进行拆分，每个组件都有相应的接口，组件之间通过接口通信，这样就可以方便地替换组件了。比如我们可以选择不同连接器类型，只要这些连接器组件实现同一个接口就行。
- 第二个是Web容器提供一个载体把组件组装在一起工作。组件的工作无非就是处理请求，因此容器通过责任链模式把请求依次交给组件去处理。对于用户来说，我只需要告诉Web容器由哪些组件来处理请求。把组件组织起来需要一个“管理者”，这就是为什么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容器设计了自己类加载器，我会在专栏后面的文章详细介绍Tomcat的类加载器。

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

## 组件的生命周期管理

不同类型的组件具有父子层次关系，父组件处理请求后再把请求传递给某个子组件。你可能会感到疑惑，Jetty的中Handler不是一条链吗，看上去像是平行关系？其实不然，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的生命周期过程：

<img src="https://static001.geekbang.org/resource/image/7f/3d/7f87b5f06cef33af6266ae7f6dcf203d.png" alt="">

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

具体到组件的设计的与实现，Tomcat和Jetty都大量采用了骨架抽象类和模板模式。比如说Tomcat中ProtocolHandler接口，ProtocolHandler有抽象基类AbstractProtocol，它实现了协议处理层的骨架和通用逻辑，而具体协议也有抽象基类，比如HttpProtocol和AjpProtocol。对于Jetty来说，Handler接口之下有AbstractHandler，Connector接口之下有AbstractConnector，这些抽象骨架类实现了一些通用逻辑，并且会定义一些抽象方法，这些抽象方法由子类实现，抽象骨架类调用抽象方法来实现骨架逻辑。

这是一个通用的设计规范，不管是Web容器还是Spring，甚至JDK本身都到处使用这种设计，比如Java集合中的AbstractSet、AbstractMap等。 值得一提的是，从Java 8开始允许接口有default方法，这样我们可以把抽象骨架类的通用逻辑放到接口中去。

## 本期精华

今天我总结了Tomcat和Jetty的组件化设计，我们可以通过搭积木的方式来定制化自己的Web容器。Web容器为了支持这种组件化设计，遵循了一些规范，比如面向接口编程，用“管理者”去组装这些组件，用反射的方式动态的创建组件、统一管理组件的生命周期，并且给组件生命状态的变化提供了扩展点，组件的具体实现一般遵循骨架抽象类和模板模式。

通过今天的学习，你会发现Tomcat和Jetty有很多共同点，并且Spring框架的设计也有不少相似的的地方，这正好说明了Web开发中有一些本质的东西是相通的，只要你深入理解了一个技术，也就是在一个点上突破了深度，再扩展广度就不是难事。并且我建议在学习一门技术的时候，可以回想一下之前学过的东西，是不是有相似的地方，有什么不同的地方，通过对比理解它们的本质，这样我们才能真正掌握这些技术背后的精髓。

## 课后思考

在我们的实际项目中，可能经常遇到改变需求，那如果采用组件化设计，当需求更改时是不是会有一些帮助呢？

不知道今天的内容你消化得如何？如果还有疑问，请大胆的在留言区提问，也欢迎你把你的课后思考和心得记录下来，与我和其他同学一起讨论。如果你觉得今天有所收获，欢迎你把它分享给你的朋友。


