You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

53 lines
8.2 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 11 | 总结从Tomcat和Jetty中提炼组件化设计规范
在当今的互联网时代,我们每个人获取信息的机会基本上都是平等的,但是为什么有些人对信息理解得更深,并且有自己独到的见解呢?我认为是因为他们养成了思考和总结的好习惯。当我们学习一门技术的时候,如果可以勤于思考、善于总结,可以帮助我们看到现象背后更本质的东西,让我们在成长之路上更快“脱颖而出”。
我们经常谈敏捷、快速迭代和重构这些都是为了应对需求的快速变化也因此我们在开始设计一个系统时就要考虑可扩展性。那究竟该怎样设计才能适应变化呢或者要设计成什么样后面才能以最小的成本进行重构呢今天我来总结一些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的用户可以通过配置文件或者注解的方式来组装BeanBean与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的生命周期过程
![](https://static001.geekbang.org/resource/image/7f/3d/7f87b5f06cef33af6266ae7f6dcf203d.png)
## 组件的骨架抽象类和模板模式
具体到组件的设计的与实现Tomcat和Jetty都大量采用了骨架抽象类和模板模式。比如说Tomcat中ProtocolHandler接口ProtocolHandler有抽象基类AbstractProtocol它实现了协议处理层的骨架和通用逻辑而具体协议也有抽象基类比如HttpProtocol和AjpProtocol。对于Jetty来说Handler接口之下有AbstractHandlerConnector接口之下有AbstractConnector这些抽象骨架类实现了一些通用逻辑并且会定义一些抽象方法这些抽象方法由子类实现抽象骨架类调用抽象方法来实现骨架逻辑。
这是一个通用的设计规范不管是Web容器还是Spring甚至JDK本身都到处使用这种设计比如Java集合中的AbstractSet、AbstractMap等。 值得一提的是从Java 8开始允许接口有default方法这样我们可以把抽象骨架类的通用逻辑放到接口中去。
## 本期精华
今天我总结了Tomcat和Jetty的组件化设计我们可以通过搭积木的方式来定制化自己的Web容器。Web容器为了支持这种组件化设计遵循了一些规范比如面向接口编程用“管理者”去组装这些组件用反射的方式动态的创建组件、统一管理组件的生命周期并且给组件生命状态的变化提供了扩展点组件的具体实现一般遵循骨架抽象类和模板模式。
通过今天的学习你会发现Tomcat和Jetty有很多共同点并且Spring框架的设计也有不少相似的的地方这正好说明了Web开发中有一些本质的东西是相通的只要你深入理解了一个技术也就是在一个点上突破了深度再扩展广度就不是难事。并且我建议在学习一门技术的时候可以回想一下之前学过的东西是不是有相似的地方有什么不同的地方通过对比理解它们的本质这样我们才能真正掌握这些技术背后的精髓。
## 课后思考
在我们的实际项目中,可能经常遇到改变需求,那如果采用组件化设计,当需求更改时是不是会有一些帮助呢?
不知道今天的内容你消化得如何?如果还有疑问,请大胆的在留言区提问,也欢迎你把你的课后思考和心得记录下来,与我和其他同学一起讨论。如果你觉得今天有所收获,欢迎你把它分享给你的朋友。