← 返回题目列表

适配器模式在 JDK 和 Spring 中有哪些典型应用?

高频 中等 第 12 / 25 题 更新于 2026/07/28
适配器模式JDKSpring MVCHandlerAdapter设计模式

简化版

适配器模式在 JDK 和 Spring 中很常见,比如 Java I/O 中 InputStreamReader 把字节流适配成字符流,事件监听中的 WindowAdapter 提供默认适配,Spring MVC 的 HandlerAdapter 把不同类型的 Handler 适配成统一调用方式。它们都体现了接口转换或调用方式统一的思想。

详细版

典型例子包括:

  1. InputStreamReader:把 InputStream 字节输入流适配成 Reader 字符输入流;
  2. OutputStreamWriter:把 OutputStream 字节输出流适配成 Writer 字符输出;
  3. WindowAdapter:为 WindowListener 多方法接口提供默认空实现;
  4. Spring MVC HandlerAdapter:让 DispatcherServlet 能以统一方式调用不同类型的处理器;
  5. Spring 中各种适配器类:把用户实现、框架回调、不同处理器形态适配到统一流程中。

回答时要注意边界:有些例子体现的是适配器思想,不一定完全等同于课本 UML。比如 Spring MVC 的 HandlerAdapter 更偏框架调用适配,它的重点是让前端控制器不用关心具体 Handler 类型。

完整版教学

一、InputStreamReader:字节流到字符流的适配

InputStream 处理的是字节:

InputStream input = new FileInputStream("a.txt");

Reader 处理的是字符:

Reader reader = new InputStreamReader(input, StandardCharsets.UTF_8);

InputStreamReader 的作用是把字节输入流适配成字符读取接口。客户端使用 Reader 的方式读取字符,内部则从 InputStream 读字节并按字符集解码。

这就是典型的接口转换:已有对象是字节流,客户端需要字符流。

二、OutputStreamWriter:字符输出到字节输出的适配

OutputStreamWriterInputStreamReader 方向相反。

客户端希望按字符写:

Writer writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);

内部实际要写到字节输出流。OutputStreamWriter 负责把字符按指定编码转换成字节。

这类例子说明适配器不只是方法名转换,还可能包含格式转换、编码转换和数据表示转换。

三、WindowAdapter:默认适配器

Java 事件监听器常常有多个方法。开发者可能只关心其中一个。

WindowAdapter 提供空实现,让开发者只重写关心的方法。

这种是默认适配器,也叫缺省适配器。它解决的是“接口方法太多,实现类被迫写很多空方法”的问题。

它和普通对象适配器不同,但都属于适配器思想的应用。

四、Spring MVC HandlerAdapter:统一调用不同 Handler

Spring MVC 中,DispatcherServlet 是前端控制器。它要处理不同形式的 Handler。

问题是:不同 Handler 的调用方式可能不同。DispatcherServlet 不应该写大量 if else 去判断每种 Handler 怎么调用。

所以 Spring MVC 引入 HandlerAdapter

DispatcherServlet -> HandlerAdapter -> 具体 Handler

DispatcherServlet 找到合适的 HandlerAdapter 后,通过适配器统一执行 Handler。

这体现了适配器模式的核心价值:框架主流程面向统一接口,具体差异由适配器处理。

五、框架适配器常常不只是简单转发

在框架中,适配器可能还会做很多工作:

  • 参数解析;
  • 返回值处理;
  • 异常转换;
  • 生命周期对接;
  • 上下文传递;
  • 反射调用。

但它的核心仍然是适配:把用户提供的不同形态对象纳入框架统一执行流程。

六、回答框架应用题时要避免绝对化

很多框架类名带 Adapter,但不一定严格等于 GoF 适配器;很多类名不带 Adapter,也可能体现适配器思想。

面试中更稳妥的说法是:

这些例子体现了适配器模式的接口转换或调用统一思想,具体实现会结合模板方法、回调、反射等机制。

这样既准确,也能体现你理解设计模式不是死背类名。

七、框架适配器背后的统一入口

Spring MVC 面对多种 Handler,DispatcherServlet 先找 supports=true 的 HandlerAdapter,再用统一 handle 入口调用具体 Handler。

request -> DispatcherServlet -> find HandlerAdapter -> handle(handler) -> ModelAndView

适配器的验收标准不是“调用成功”,而是 Target 的业务语义在转换前后保持成立。参数、单位、时区、精度、空值、错误码和资源所有权都应逐项形成契约样例,否则最危险的错误会以“成功响应”的形式潜伏。

八、语义边界与契约样例验证

检查维度应确认的内容
机制正确性框架例子要说清谁是 Target、谁是 Adaptee,以及转换了什么。
适用边界InputStreamReader 做字节到字符解码,需要显式 Charset;WindowAdapter 则用空实现减少监听接口的实现负担,两者适配目的不同。
测试证据通过框架统一入口传入不同 Adaptee,验证 supports 选择、调用转换和返回模型
工程代价重点统计字段映射与对象分配成本、外部模型变更影响面,以及多层版本适配造成的认知债务

易错点:框架例子要说清谁是 Target、谁是 Adaptee,以及转换了什么。

九、常见误区与追问

  • 误区:InputStreamReader 只是两个流的简单转发。 它使用 CharsetDecoder 把字节解码为字符,字符边界和错误策略都是真实语义转换。
  • 误区:适配器只需要让方法签名能够编译。 真正的适配还包括单位、时区、错误码、空值、资源释放和幂等语义;签名一致不代表行为兼容。
  • 追问:HandlerAdapter 为什么不是简单调用同名方法? 不同 Handler 类型调用方式不同,它先判断支持性,再统一调用并适配返回模型。
  • 追问:适配器应该放在哪一层? 通常放在系统边界或防腐层,让业务层只依赖 Target,避免第三方模型和异常向内扩散。
  • 追问:适配器测试为什么需要契约样例? 因为类型相同仍可能单位、精度或错误语义不同;契约样例能证明转换后的行为而不只是编译通过。

十、加强记忆

JDK 里记 InputStreamReaderOutputStreamWriterWindowAdapter,Spring 里记 HandlerAdapter。前两个是字节流和字符流的转换,WindowAdapter 是默认适配,HandlerAdapter 是框架统一调用不同 Handler。核心都围绕“接口或调用方式不一致,需要中间层适配”。