Day678.从Tomcat和Jetty中提炼组件化设计规范 -深入拆解 Tomcat & Jetty

Posted 阿昌喜欢吃黄桃

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day678.从Tomcat和Jetty中提炼组件化设计规范 -深入拆解 Tomcat & Jetty相关的知识,希望对你有一定的参考价值。

从Tomcat和Jetty中提炼组件化设计规范

Hi,我是阿昌,今天学习的是关于从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 容器设计了自己类加载器。

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 的生命周期过程:


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

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

比如说 Tomcat 中 ProtocolHandler 接口,ProtocolHandler 有抽象基类 AbstractProtocol,它实现了协议处理层的骨架和通用逻辑,而具体协议也有抽象基类,比如 HttpProtocol 和 AjpProtocol。

对于 Jetty 来说,Handler 接口之下有 AbstractHandler,Connector 接口之下有 AbstractConnector,这些抽象骨架类实现了一些通用逻辑,并且会定义一些抽象方法,这些抽象方法由子类实现,抽象骨架类调用抽象方法来实现骨架逻辑。

这是一个通用的设计规范,不管是 Web 容器还是 Spring,甚至 JDK 本身都到处使用这种设计,比如 Java 集合中的 AbstractSet、AbstractMap 等。

值得一提的是,从 Java 8 开始允许接口有 default 方法,这样我们可以把抽象骨架类的通用逻辑放到接口中去。


五、总结

通过搭积木的方式来定制化自己的 Web 容器。

Web 容器为了支持这种组件化设计,遵循了一些规范,比如面向接口编程,用“管理者”去组装这些组件,用反射的方式动态的创建组件、统一管理组件的生命周期,并且给组件生命状态的变化提供了扩展点,组件的具体实现一般遵循骨架抽象类和模板模式。

发现 Tomcat 和 Jetty 有很多共同点,并且 Spring 框架的设计也有不少相似的的地方,这正好说明了 Web 开发中有一些本质的东西是相通的,只要你深入理解了一个技术,也就是在一个点上突破了深度,再扩展广度就不是难事。


以上是关于Day678.从Tomcat和Jetty中提炼组件化设计规范 -深入拆解 Tomcat & Jetty的主要内容,如果未能解决你的问题,请参考以下文章

Day676.Jetty架构之Connector组件 -深入拆解 Tomcat & Jetty

Day677.Jetty架构之Handler组件 -深入拆解 Tomcat & Jetty

Day700.Cluster组件:Tomcat的集群通信原理 -深入拆解 Tomcat & Jetty

Day695.Spring Boot如何使用内嵌式的Tomcat和Jetty -深入拆解 Tomcat & Jetty

Day684.Executor组件:Tomcat如何扩展Java线程池 -深入拆解 Tomcat & Jetty

Day683.AprEndpoint组件:Tomcat APR提高I/O性能的秘密 -深入拆解 Tomcat & Jetty