Tomcat 架构原理解析到架构设计借鉴 (5)

连接器中的 Adapter 会调用容器的 Service 方法来执行 Servlet,最先拿到请求的是 Engine 容器,Engine 容器对请求做一些处理后,会把请求传给自己子容器 Host 继续处理,依次类推,最后这个请求会传给 Wrapper 容器,Wrapper 会调用最终的 Servlet 来处理。那么这个调用过程具体是怎么实现的呢?答案是使用 Pipeline-Valve 管道。

Pipeline-Valve 是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将再调用下一个处理者继续处理,Valve 表示一个处理点(也就是一个处理阀门),因此 invoke方法就是来处理请求的。

public interface Valve { public Valve getNext(); public void setNext(Valve valve); public void invoke(Request request, Response response) }

继续看 Pipeline 接口

public interface Pipeline { public void addValve(Valve valve); public Valve getBasic(); public void setBasic(Valve valve); public Valve getFirst(); }

Pipeline中有 addValve方法。Pipeline 中维护了 Valve链表,Valve可以插入到 Pipeline中,对请求做某些处理。我们还发现 Pipeline 中没有 invoke 方法,因为整个调用链的触发是 Valve 来完成的,Valve完成自己的处理后,调用 getNext.invoke() 来触发下一个 Valve 调用。

其实每个容器都有一个 Pipeline 对象,只要触发了这个 Pipeline 的第一个 Valve,这个容器里 Pipeline中的 Valve 就都会被调用到。但是,不同容器的 Pipeline 是怎么链式触发的呢,比如 Engine 中 Pipeline 需要调用下层容器 Host 中的 Pipeline。

这是因为 Pipeline中还有个 getBasic方法。这个 BasicValve处于 Valve链表的末端,它是 Pipeline中必不可少的一个 Valve,负责调用下层容器的 Pipeline 里的第一个 Valve。

整个过程分是通过连接器中的 CoyoteAdapter 触发,它会调用 Engine 的第一个 Valve:

@Override public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) { // 省略其他代码 // Calling the container connector.getService().getContainer().getPipeline().getFirst().invoke( request, response); ... }

Wrapper 容器的最后一个 Valve 会创建一个 Filter 链,并调用 doFilter() 方法,最终会调到 Servlet的 service方法。

前面我们不是讲到了 Filter,似乎也有相似的功能,那 Valve 和 Filter有什么区别吗?它们的区别是:

Valve是 Tomcat的私有机制,与 Tomcat 的基础架构 API是紧耦合的。Servlet API是公有的标准,所有的 Web 容器包括 Jetty 都支持 Filter 机制。

另一个重要的区别是 Valve工作在 Web 容器级别,拦截所有应用的请求;而 Servlet Filter 工作在应用级别,只能拦截某个 Web 应用的所有请求。如果想做整个 Web容器的拦截器,必须通过 Valve来实现。

Lifecycle 生命周期

前面我们看到 Container容器 继承了 Lifecycle 生命周期。如果想让一个系统能够对外提供服务,我们需要创建、组装并启动这些组件;在服务停止的时候,我们还需要释放资源,销毁这些组件,因此这是一个动态的过程。也就是说,Tomcat 需要动态地管理这些组件的生命周期。

如何统一管理组件的创建、初始化、启动、停止和销毁?如何做到代码逻辑清晰?如何方便地添加或者删除组件?如何做到组件启动和停止不遗漏、不重复?

一键式启停:LifeCycle 接口

设计就是要找到系统的变化点和不变点。这里的不变点就是每个组件都要经历创建、初始化、启动这几个过程,这些状态以及状态的转化是不变的。而变化点是每个具体组件的初始化方法,也就是启动方法是不一样的。

因此,Tomcat 把不变点抽象出来成为一个接口,这个接口跟生命周期有关,叫作 LifeCycle。LifeCycle 接口里定义这么几个方法:init()、start()、stop() 和 destroy(),每个具体的组件(也就是容器)去实现这些方法。

在父组件的 init() 方法里需要创建子组件并调用子组件的 init() 方法。同样,在父组件的 start()方法里也需要调用子组件的 start() 方法,因此调用者可以无差别的调用各组件的 init() 方法和 start() 方法,这就是组合模式的使用,并且只要调用最顶层组件,也就是 Server 组件的 init()和start() 方法,整个 Tomcat 就被启动起来了。所以 Tomcat 采取组合模式管理容器,容器继承 LifeCycle 接口,这样就可以向针对单个对象一样一键管理各个容器的生命周期,整个 Tomcat 就启动起来。

可扩展性:LifeCycle 事件

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjzfz.html