连接器对 Servlet 容器屏蔽了协议及 I/O 模型等的区别,无论是 HTTP 还是 AJP,在容器中获取到的都是一个标准的 ServletRequest 对象。连接器封装了底层的网络通信信息,包含Socket请求及响应处理,为容器(Catalinna)提供了统一的接口,容器从连接器拿到的是连接器封装后的请求对象ServletRequest,使容器与具体的请求协议以及I/O操作方式完全解耦.容器处理完请求后,容器通过连接器提供的Response对象将结果下入输出流中。
连接器的核心组件
可以看出连接器需要完成的三个高内聚功能是网络通信,应用层协议解析,Tomcat Request/Response 与 ServletRequest/ServletResponse 的转化。而Tomcat的设计者通过Endpoint、Processor 和 Adapter这三个组件来实现这三个功能的, 其中 Endpoint 和 Processor 放在一起抽象成了 ProtocolHandler 组件,它们的关系如下图所示
Endpoint 负责提供字节流给 Processor,Processor 负责提供 Tomcat Request 对象给 Adapter,Adapter 负责提供 ServletRequest 对象给容器。这样功能层次明确的实现了低耦合、高内聚的内部功能。
而至于为什么将Endpoint 和 Processor 放在一起抽象成ProtocolHandler 组件是因为Tomcat的
I/O 模型和应用层协议可以自由组合,比如 NIO + HTTP 或者 NIO.2 + AJP。所以设计者们将网络通信和应用层协议放在一起考虑,最终设计了ProtocolHandler接口来封装这两种变化点。各种协议和通信模型的组合有相应的具体实现类。比如:Http11NioProtocol 和AjpNioProtocol。
除了这些变化点,系统也存在一些相对稳定的部分,因此 Tomcat 设计了一系列抽象基类来封装这些稳定的部分,抽象基类 AbstractProtocol 实现了 ProtocolHandler 接口。每一种应用层协议有自己的抽象基类,比如 AbstractAjpProtocol 和 AbstractHttp11Protocol,具体协议的实现类扩展了协议层抽象基类。
它们的继承关系:
ProtocolHandler
连接器的ProtocolHandler是用于处理网络连接和应用层协议的,其包含了两个组件:Endpoint 和 Processor。
Endpoint
Processor
从图中看到,Endpoint 接收到 Socket 连接后,生成一个 SocketProcessor 任务提交到线程池去处理,SocketProcessor 的 run 方法会调用 Processor 组件去解析应用层协议,Processor 通过解析生成 Request 对象后,会调用 Adapter 的 Service 方法。到这里我们学习了 ProtocolHandler 的总体架构和工作原理,关于 Endpoint 的详细设计,后面我还会专门介绍 Endpoint 是如何最大限度地利用 Java NIO 的非阻塞以及 NIO.2 的异步特性,来实现高并发。
Adapter 组件
由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat 定义了自己的 Request 类来“存放”这些请求信息。ProtocolHandler 接口负责解析请求并生成 Tomcat Request 类。但是这个 Request 对象不是标准的 ServletRequest,也就意味着,不能用 Tomcat Request 作为参数来调用容器。Tomcat 设计者的解决方案是引入 CoyoteAdapter,这是适配器模式的经典运用,连接器调用 CoyoteAdapter 的 sevice 方法,传入的是 Tomcat Request 对象,CoyoteAdapter 负责将 Tomcat Request 转成 ServletRequest,再调用容器的 service 方法。