什么是适配器模式

适配器模式其实就像我们日常生活中的 "转换器"—— 比如你去欧洲旅行时,手机充电器的插头和当地插座不匹配,这时候就需要一个 "电源适配器" 来转换接口。在编程世界里,适配器模式干的是同样的事儿:当两个原本接口不兼容的组件需要协作时,用一个 "中间转换器" 来完成接口的适配,让它们能像齿轮一样咬合工作。

我们以一个打印机的开发为例来进一步理解。假设我们有一个旧打印机接口OldPrinter,只能接收字符串打印:

interface OldPrinter {
    void printString(String text);
}

后来我们又买了新打印机NewPrinter,只支持打印字节数组:

interface NewPrinter {
    void printBytes(byte[] data);
}

这时候如果直接用新打印机,原来的代码会因为接口不匹配而报错。这时候适配器就派上用场了:

// 适配器:把新打印机的接口转换成旧接口需要的形式
class PrinterAdapter implements OldPrinter {
    private NewPrinter newPrinter;
    
    public PrinterAdapter(NewPrinter newPrinter) {
        this.newPrinter = newPrinter;
    }
    
    @Override
    public void printString(String text) {
        // 将字符串转为字节数组,适配新打印机的接口
        byte[] data = text.getBytes();
        newPrinter.printBytes(data);
    }
}

这样一来,旧代码不用修改,通过适配器就能直接使用新打印机 —— 这就是适配器模式的核心思想:不修改原有组件,通过中间层实现接口转换

从这个例子可以看出,适配器模式主要涉及到三个角色:

  • 目标接口(Target):定义客户端所期望的接口,它可以是一个全新的接口,也可以是一个已经存在的接口标准。在上述例子中,OldPrinter接口就是目标接口。

  • 被适配者(Adaptee):是现有系统中已经存在的接口或类,但其接口与目标接口不兼容。这里的NewPrinter类就是被适配者。

  • 适配器(Adapter):实现了目标接口,同时持有被适配者的引用。在其方法中,通过调用被适配者的相应功能,将被适配者的接口转换为目标接口。PrinterAdapter类就是适配器。

在 Java 开发中,适配器模式有着广泛的应用场景,比如在不同数据源对接时,可能需要连接 MySQL、Oracle、MongoDB 等多种数据库,适配器模式可以创建针对每种数据源的适配器类,使它们都实现统一的数据库访问接口,让上层业务代码无需关注底层数据源的具体差异;在整合多种第三方 API 时,也可以通过适配器模式将这些 API 接口转换为项目内部统一的接口规范,便于管理和切换不同的服务提供商 。

适配器模式在 Java 开发中是一种非常实用的设计模式,它能够有效地解决接口不兼容的问题,提高代码的复用性和可维护性,使系统更加灵活和易于扩展。

Tomcat中的CoyoteAdapter

在 Tomcat 的架构体系中,CoyoteAdapter 处于连接器与容器的衔接层,扮演着关键的适配器角色。一方面,Servlet 规范定义了标准的HttpServletRequestHttpServletResponse接口,另一方面,Tomcat 自身的 Coyote 组件定义了底层的RequestResponse实现。而CoyoteAdapter正是完成了这两个接口的适配,并且使连接器和容器解耦。

具体来说,CoyoteAdapter 的主要作用体现在以下几个关键方面:

请求协议转换

由于不同的客户端可能使用不同的协议与服务器进行通信,而 Tomcat 内部处理请求需要统一的标准。CoyoteAdapter 承担起了将不同协议的请求转换为 Tomcat 内部统一的 ServletRequest 对象的重任。例如,当使用 AJP 协议的客户端请求到达时,CoyoteAdapter 会将 AJP 协议的请求信息解析并转换为符合 Tomcat 内部规范的 ServletRequest 对象,确保容器能够正确地处理这些请求,而无需关心请求最初的协议形式 。

连接解耦

CoyoteAdapter 有效地将连接器和容器进行解耦,使得它们可以独立发展和演化。连接器专注于网络通信和请求的接收与初步解析,容器专注于 Servlet 的管理和业务逻辑的执行。通过 CoyoteAdapter 的适配,即使连接器的实现或协议发生变化,或者容器的内部结构和处理方式进行调整,都不会对彼此产生直接的影响,大大提高了 Tomcat 架构的灵活性和可维护性 。这就好比汽车的发动机和变速器,通过一个合适的传动装置(类似 CoyoteAdapter)连接,发动机和变速器可以分别进行技术升级和改进,而不影响整车的运行。

调用分发

当请求被转换为 ServletRequest 对象后,CoyoteAdapter 负责将其分发到合适的容器组件进行处理。它会根据请求的相关信息,如 URL、Host 等,确定请求应该被转发到哪个 Engine、Host、Context 以及具体的 Wrapper 中的 Servlet 进行处理 。例如,当用户请求访问一个特定的 Web 应用下的某个 Servlet 时,CoyoteAdapter 会根据请求的 URL 和相关配置信息,准确地将请求分发到对应的 Context 中的 Wrapper,进而调用该 Servlet 的 service 方法来处理请求。

在 Tomcat 架构中,CoyoteAdapter 是一个关键的纽带,通过实现请求协议转换、连接解耦和调用分发等重要功能,确保了 Tomcat 能够高效、稳定地处理各种客户端请求,为 Java Web 应用的运行提供了坚实的基础。

CoyoteAdapter 中适配器模式的实现

代码实现

在 CoyoteAdapter 类中,进行适配的关键方法是service方法,以下是对该方法的简单解释:

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {

   // 从Coyote请求对象的属性中获取已经适配过的Request对象,如果不存在则创建新的
   Request request = (Request) req.getNote(ADAPTER_NOTES);
   Response response = (Response) res.getNote(ADAPTER_NOTES);
   // org.apache.coyote.Request 转 org.apache.catalina.connector.Request(HttpServletRequest的实现类)
   // 执行服务方法,将请求分发给容器进行处理。这里传递的就是org.apache.catalina.connector.Request
   connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

}
  1. 获取或创建适配后的请求和响应对象:首先,通过req.getNote(ADAPTER_NOTES)尝试从 Coyote 请求对象的属性中获取已经适配过的Request对象(这里的Request是符合容器处理要求的类型),同时获取对应的Response对象。如果获取到的对象为null,则调用connector.createRequest()connector.createResponse()方法创建新的请求和响应对象 。然后,通过一系列的set方法,建立起 Coyote 请求响应对象与适配后的请求响应对象之间的关联。例如,request.setCoyoteRequest(req)将 Coyote 请求对象设置到适配后的请求对象中,这样在后续处理中可以方便地获取底层网络通信相关的信息;request.setResponse(response)response.setRequest(request)则建立起请求和响应对象之间的双向关联 。最后,将适配后的请求和响应对象设置回 Coyote 请求和响应对象的属性中,以便后续使用。

  2. 请求分发:在完成请求和响应对象的适配和关联后,通过connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)这一长串调用,将适配后的请求和响应对象传递给容器的管道(Pipeline)进行处理 。容器的管道是一个责任链模式的应用,其中包含多个阀门(Valve),每个阀门都有机会对请求进行处理,最终将请求传递到合适的 Servlet 进行业务逻辑处理 。

  3. 资源清理:在请求处理完成后,无论是否发生异常,都会执行finally块中的代码。通过request.recycle()response.recycle()方法,对请求和响应对象进行资源回收和清理,为下一次请求处理做好准备,提高系统的资源利用率 。

通过以上service方法的代码逻辑,CoyoteAdapter 成功地将 Coyote 请求对象适配为容器能够处理的请求对象,并实现了请求的分发和资源的有效管理 。

适配模式的体现

从结构上看,CoyoteAdapter 类实现了适配器模式的典型结构 。在适配器模式中,存在目标接口(Target)、被适配者(Adaptee)和适配器(Adapter)三个主要角色。在 Tomcat 中,javax.servlet.http.HttpServletRequestjavax.servlet.http.HttpServletResponse可以看作是目标接口,它们定义了容器处理请求和响应所需的标准接口 。org.apache.coyote.Requestorg.apache.coyote.Response则是被适配者,它们是连接器产生的原始请求和响应对象,但接口与目标接口不兼容 。CoyoteAdapter 类充当了适配器的角色,它持有对连接器(包含被适配者相关信息)的引用,并且实现了将org.apache.coyote.Requestorg.apache.coyote.Response转换为javax.servlet.http.HttpServletRequestjavax.servlet.http.HttpServletResponse的功能 。

在工作原理上,当连接器接收到请求后,将org.apache.coyote.Requestorg.apache.coyote.Response传递给 CoyoteAdapter 。CoyoteAdapter 通过其内部的service方法,对这些对象进行适配处理,将其转换为符合容器要求的RequestResponse对象(实际上是实现了目标接口的对象) 。然后,CoyoteAdapter 将适配后的对象传递给容器进行进一步处理,就像适配器模式中描述的那样,通过适配器将不兼容的接口转换为兼容的接口,使得原本无法协同工作的连接器和容器能够顺利地进行交互和合作 。

CoyoteAdapter 类从结构和工作原理上都完美地体现了适配器模式,通过这种设计模式,有效地解决了 Tomcat 中不同组件之间接口不兼容的问题,提高了系统的灵活性和可扩展性 。

总结

CoyoteAdapter 类在 Tomcat 中对适配器模式的使用,是设计模式在实际项目中成功应用的典范。它巧妙地解决了 Tomcat 架构中连接器和容器之间接口不兼容的问题,通过将连接器产生的请求对象适配为容器能够处理的格式,确保了整个 Web 服务器系统的高效运行 。从实际应用案例可以看出,CoyoteAdapter 的存在极大地提高了 Tomcat 的灵活性和可扩展性,使得不同协议的请求都能被统一处理,不同的容器组件也能独立发展和演化 。

文章作者: Z
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 微博客
性能调优
喜欢就支持一下吧