适配器模式:Spring MVC HandlerAdapter的适配器链设计
适配器模式:Spring MVC HandlerAdapter的适配器链设计
适读人群:中高级Java开发者 | 阅读时长:约22分钟 | 模式类型:结构型
开篇故事
做了这么多年 Web 开发,Spring MVC 一定是大家最熟悉的框架之一。但你有没有想过,Spring MVC 为什么能同时支持这么多种"控制器"写法?
// 写法一:@Controller + @RequestMapping(最常见)
@Controller
public class OrderController {
@RequestMapping("/order/list")
public String listOrders(Model model) {
return "order/list";
}
}
// 写法二:实现 HttpRequestHandler 接口
public class StaticFileHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest req, HttpServletResponse res) {
// 直接处理HTTP请求
}
}
// 写法三:实现 Controller 接口(Spring早期风格)
public class LegacyController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse res) {
return new ModelAndView("view");
}
}这三种写法的 Handler 接口完全不同,但 Spring MVC 的 DispatcherServlet 只有一个 doDispatch() 方法,它是怎么统一处理这些不同接口的 Handler 的?
答案就是适配器模式。Spring MVC 为每种 Handler 类型提供了对应的 HandlerAdapter,DispatcherServlet 通过适配器来调用不同类型的 Handler,对 DispatcherServlet 来说,它只知道"调用适配器的 handle() 方法",完全不需要关心底层 Handler 的具体类型。
一、模式动机:统一不兼容的接口
适配器模式(Adapter Pattern)的核心:将一个类的接口转换成客户端所期待的另一种接口,使原本因接口不兼容而无法一起工作的类可以协同工作。
经典场景:
- 整合第三方库:第三方库的接口与我们系统的接口不兼容,适配器将其包装成我们需要的格式。
- 遗留代码整合:老系统有一套接口,新系统有另一套标准,适配器让两者可以无缝对接。
- 框架扩展点:Spring MVC 的
HandlerAdapter就是这类场景的经典实现,允许不同风格的 Handler 通过适配器接入统一的处理流程。
适配器分两种:
- 对象适配器:持有被适配类的实例,通过组合实现适配(更灵活,推荐使用)
- 类适配器:继承被适配类,通过继承实现适配(Java 单继承限制,使用较少)
二、模式结构
对应到 Spring MVC:
Target=HandlerAdapter接口Adaptee= 各种 Handler(@Controller、HttpRequestHandler、Controller)Adapter=RequestMappingHandlerAdapter、HttpRequestHandlerAdapter、SimpleControllerHandlerAdapterClient=DispatcherServlet
三、Spring MVC HandlerAdapter 源码深度分析
3.1 HandlerAdapter 接口
public interface HandlerAdapter {
/**
* 判断此适配器是否支持给定的Handler
* 这是适配器模式中的关键方法:适配器自己决定能适配哪种Handler
*/
boolean supports(Object handler);
/**
* 使用给定的Handler处理请求(核心适配方法)
* 返回ModelAndView,或对于完全处理完的响应返回null
*/
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception;
/**
* 返回Handler的最后修改时间(用于HTTP缓存控制)
*/
long getLastModified(HttpServletRequest request, Object handler);
}3.2 DispatcherServlet 的适配器选择逻辑
// DispatcherServlet.doDispatch() 核心逻辑(简化版)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1. 获取Handler(HandlerMapping负责)
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
// 2. 获取适合此Handler的HandlerAdapter(适配器模式的关键)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 通过适配器执行Handler(DispatcherServlet只与HandlerAdapter打交道)
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 4. 渲染视图(略)
}
// 适配器选择逻辑:遍历所有HandlerAdapter,找到第一个supports()返回true的
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) { // 问每个适配器:你能处理这个Handler吗?
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler + "]");
}3.3 三种核心 HandlerAdapter 的实现对比
SimpleControllerHandlerAdapter(适配实现了 Controller 接口的 Handler):
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof Controller; // 只处理实现了Controller接口的Handler
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
// 将handler强转为Controller接口,调用其handleRequest方法
return ((Controller) handler).handleRequest(request, response);
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified lastModified) {
return lastModified.getLastModified(request);
}
return -1L;
}
}HttpRequestHandlerAdapter(适配实现了 HttpRequestHandler 接口的 Handler):
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof HttpRequestHandler;
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
((HttpRequestHandler) handler).handleRequest(request, response);
return null; // HttpRequestHandler直接写响应,不返回ModelAndView
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified lastModified) {
return lastModified.getLastModified(request);
}
return -1L;
}
}RequestMappingHandlerAdapter(适配 @RequestMapping 注解标注的方法):这是最复杂的适配器,核心是通过反射调用被 @RequestMapping 标注的方法,并处理参数绑定、返回值转换等。
四、生产级代码实现
4.1 支付渠道适配器系统
/**
* 统一支付接口(Target)
* 这是我们系统内部定义的标准支付接口
*/
public interface PaymentGateway {
/**
* 发起支付
*/
PaymentResult pay(PaymentRequest request);
/**
* 查询支付状态
*/
PaymentStatus query(String transactionId);
/**
* 发起退款
*/
RefundResult refund(RefundRequest request);
/**
* 处理回调通知
*/
CallbackResult handleCallback(Map<String, String> params);
/**
* 支持的支付渠道
*/
String channelCode();
}
/**
* 支付宝原生SDK(Adaptee A)
* 这是支付宝SDK提供的接口,我们无法修改
*/
public class AlipayNativeClient {
private final String appId;
private final String privateKey;
private final String alipayPublicKey;
public AlipayNativeClient(String appId, String privateKey, String alipayPublicKey) {
this.appId = appId;
this.privateKey = privateKey;
this.alipayPublicKey = alipayPublicKey;
}
// 支付宝SDK原生接口,和我们的PaymentGateway完全不兼容
public AlipayTradePagePayResponse tradePagePay(AlipayTradePagePayModel model) {
// 调用支付宝SDK...
return new AlipayTradePagePayResponse();
}
public AlipayTradeQueryResponse tradeQuery(String outTradeNo) {
// 查询支付状态...
return new AlipayTradeQueryResponse();
}
public AlipayTradeRefundResponse tradeRefund(AlipayTradeRefundModel model) {
// 退款...
return new AlipayTradeRefundResponse();
}
public boolean verifyNotify(Map<String, String> params) {
// 验证回调签名...
return true;
}
}
/**
* 支付宝适配器(Adapter A)
* 将AlipayNativeClient适配成PaymentGateway接口
*/
@Component
@Slf4j
public class AlipayGatewayAdapter implements PaymentGateway {
private final AlipayNativeClient alipayClient;
private final ObjectMapper objectMapper;
public AlipayGatewayAdapter(AlipayProperties properties, ObjectMapper objectMapper) {
this.alipayClient = new AlipayNativeClient(
properties.getAppId(),
properties.getPrivateKey(),
properties.getAlipayPublicKey()
);
this.objectMapper = objectMapper;
}
@Override
public PaymentResult pay(PaymentRequest request) {
// 将我们的PaymentRequest转换为支付宝SDK的model(接口适配)
AlipayTradePagePayModel model = new AlipayTradePagePayModel();
model.setOutTradeNo(request.getOrderId());
model.setTotalAmount(request.getAmount().toString());
model.setSubject(request.getSubject());
model.setBody(request.getDescription());
model.setReturnUrl(request.getReturnUrl());
model.setNotifyUrl(request.getNotifyUrl());
log.info("Calling Alipay tradePagePay, orderId: {}", request.getOrderId());
try {
AlipayTradePagePayResponse response = alipayClient.tradePagePay(model);
// 将支付宝响应转换为我们统一的PaymentResult(结果适配)
if (response.isSuccess()) {
return PaymentResult.builder()
.success(true)
.transactionId(response.getTradeNo())
.payUrl(response.getBody()) // 支付宝返回的是HTML表单或支付链接
.channelCode(channelCode())
.build();
} else {
return PaymentResult.builder()
.success(false)
.errorCode(response.getCode())
.errorMessage(response.getMsg() + ": " + response.getSubMsg())
.channelCode(channelCode())
.build();
}
} catch (Exception e) {
log.error("Alipay pay failed, orderId: {}", request.getOrderId(), e);
return PaymentResult.builder()
.success(false)
.errorCode("SYSTEM_ERROR")
.errorMessage("Alipay communication failed: " + e.getMessage())
.channelCode(channelCode())
.build();
}
}
@Override
public PaymentStatus query(String transactionId) {
AlipayTradeQueryResponse response = alipayClient.tradeQuery(transactionId);
// 支付宝状态码转换为统一状态枚举
return switch (response.getTradeStatus()) {
case "TRADE_SUCCESS", "TRADE_FINISHED" -> PaymentStatus.SUCCESS;
case "WAIT_BUYER_PAY" -> PaymentStatus.PENDING;
case "TRADE_CLOSED" -> PaymentStatus.CANCELLED;
default -> PaymentStatus.UNKNOWN;
};
}
@Override
public RefundResult refund(RefundRequest request) {
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
model.setOutTradeNo(request.getOriginalOrderId());
model.setRefundAmount(request.getRefundAmount().toString());
model.setRefundReason(request.getReason());
model.setOutRequestNo(request.getRefundId());
AlipayTradeRefundResponse response = alipayClient.tradeRefund(model);
return RefundResult.builder()
.success(response.isSuccess())
.refundId(request.getRefundId())
.channelCode(channelCode())
.build();
}
@Override
public CallbackResult handleCallback(Map<String, String> params) {
boolean verified = alipayClient.verifyNotify(params);
if (!verified) {
return CallbackResult.invalid("Signature verification failed");
}
String orderId = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
String tradeStatus = params.get("trade_status");
return CallbackResult.builder()
.valid(true)
.orderId(orderId)
.transactionId(tradeNo)
.status(query(orderId))
.build();
}
@Override
public String channelCode() {
return "ALIPAY";
}
}
/**
* 微信支付原生SDK(Adaptee B)
*/
public class WechatPayNativeClient {
// 微信支付SDK完全不同的接口风格
public WxPayUnifiedOrderResult unifiedOrder(WxPayUnifiedOrderRequest request) {
return new WxPayUnifiedOrderResult();
}
public WxPayOrderQueryResult orderQuery(WxPayOrderQueryRequest request) {
return new WxPayOrderQueryResult();
}
public WxPayRefundResult refund(WxPayRefundRequest request) {
return new WxPayRefundResult();
}
public boolean parseOrderNotifyResult(String xmlData) {
return true;
}
}
/**
* 微信支付适配器(Adapter B)
*/
@Component
@Slf4j
public class WechatPayGatewayAdapter implements PaymentGateway {
private final WechatPayNativeClient wechatPayClient;
public WechatPayGatewayAdapter(WechatPayProperties properties) {
// 初始化微信支付客户端...
this.wechatPayClient = new WechatPayNativeClient();
}
@Override
public PaymentResult pay(PaymentRequest request) {
WxPayUnifiedOrderRequest wxRequest = new WxPayUnifiedOrderRequest();
wxRequest.setOutTradeNo(request.getOrderId());
wxRequest.setTotalFee(request.getAmount().multiply(BigDecimal.valueOf(100)).intValue()); // 微信用分
wxRequest.setBody(request.getSubject());
wxRequest.setNotifyUrl(request.getNotifyUrl());
wxRequest.setTradeType("NATIVE");
WxPayUnifiedOrderResult result = wechatPayClient.unifiedOrder(wxRequest);
return PaymentResult.builder()
.success("SUCCESS".equals(result.getReturnCode()) && "SUCCESS".equals(result.getResultCode()))
.transactionId(result.getPrepayId())
.payUrl(result.getCodeURL())
.channelCode(channelCode())
.build();
}
@Override
public PaymentStatus query(String transactionId) {
WxPayOrderQueryRequest queryRequest = new WxPayOrderQueryRequest();
queryRequest.setOutTradeNo(transactionId);
WxPayOrderQueryResult result = wechatPayClient.orderQuery(queryRequest);
return switch (result.getTradeState()) {
case "SUCCESS" -> PaymentStatus.SUCCESS;
case "NOTPAY", "USERPAYING" -> PaymentStatus.PENDING;
case "CLOSED", "REVOKED" -> PaymentStatus.CANCELLED;
case "REFUND" -> PaymentStatus.REFUNDED;
default -> PaymentStatus.UNKNOWN;
};
}
@Override
public RefundResult refund(RefundRequest request) {
// 微信退款实现...
return RefundResult.builder().success(true).channelCode(channelCode()).build();
}
@Override
public CallbackResult handleCallback(Map<String, String> params) {
// 微信回调处理...
return CallbackResult.builder().valid(true).build();
}
@Override
public String channelCode() {
return "WECHAT";
}
}
/**
* 支付网关路由器(Client)
* 统一使用PaymentGateway接口,不关心具体的支付渠道实现
*/
@Service
@Slf4j
public class PaymentService {
private final Map<String, PaymentGateway> gatewayMap;
public PaymentService(List<PaymentGateway> gateways) {
// 构建渠道Code -> 适配器的映射
this.gatewayMap = gateways.stream()
.collect(Collectors.toMap(PaymentGateway::channelCode, Function.identity()));
log.info("Registered {} payment gateways: {}", gatewayMap.size(), gatewayMap.keySet());
}
public PaymentResult initiatePayment(PaymentRequest request) {
PaymentGateway gateway = gatewayMap.get(request.getChannel());
if (gateway == null) {
throw new UnsupportedOperationException(
"Unsupported payment channel: " + request.getChannel());
}
log.info("Initiating payment via {}, orderId: {}", request.getChannel(), request.getOrderId());
return gateway.pay(request);
}
public CallbackResult handleCallback(String channel, Map<String, String> params) {
PaymentGateway gateway = gatewayMap.get(channel);
if (gateway == null) {
return CallbackResult.invalid("Unknown channel: " + channel);
}
return gateway.handleCallback(params);
}
}五、与相关模式的对比与选型
适配器 vs 装饰器
- 适配器:转换接口,被适配的对象接口和目标接口不同,目的是"兼容"。
- 装饰器:增强功能,装饰器和被装饰对象实现相同的接口,目的是"扩展"。
适配器 vs 外观模式
- 适配器:针对单个对象,将其接口转换为期望接口。
- 外观:针对一个子系统,提供简化的统一接口来访问多个复杂组件。
适配器 vs 代理模式
- 适配器:改变接口(新接口和旧接口不同)。
- 代理:保持接口不变,在调用前后添加逻辑(控制访问、增强功能)。
六、踩坑实录
坑一:适配器链顺序影响 HandlerAdapter 选择
Spring MVC 注册的 HandlerAdapter 是有优先级顺序的,RequestMappingHandlerAdapter 的优先级高于 SimpleControllerHandlerAdapter。当我们往 Spring 容器中注册了多个 HandlerAdapter 时,如果顺序不对,可能导致错误的适配器被选中。
实际踩坑:我在一个项目里自定义了一个 HandlerAdapter,想要拦截某些特定的 Handler,但因为注册顺序靠后,每次都是被 RequestMappingHandlerAdapter 先匹配上了,自定义适配器根本没机会执行。
解决方案:实现 Ordered 接口或使用 @Order 注解来控制适配器的优先级。
坑二:适配器没有处理异常转换
第三方SDK的异常类型和我们系统的异常体系不一致,直接让 SDK 异常透传出去,会导致调用方收到各种 AlipayApiException、WxPayException 等业务层不认识的异常,上层的统一异常处理也无法捕获和处理。
适配器的职责之一就是异常转换——把 Adaptee 的异常转换成 Target 期望的异常类型:
// 在适配器中统一做异常转换
try {
AlipayResponse response = alipayClient.execute(request);
// 处理正常响应
} catch (AlipayApiException e) {
// 转换为我们系统的支付异常
throw new PaymentException("Alipay API call failed", e);
}坑三:适配器引入额外的数据转换开销
在高频调用场景(比如每秒几千次的支付查询),每次调用适配器都进行数据模型转换(DTO → SDK model → DTO),这些转换的 CPU 和内存开销是不可忽视的。
优化方案:对频繁查询的结果进行缓存,减少实际调用次数;或者在热路径上的数据转换代码做性能优化(避免反射,使用直接方法调用)。
七、总结
适配器模式是系统集成时最常用的模式,无论是接入第三方 SDK 还是改造遗留系统,适配器都能优雅地解决接口不兼容问题。
Spring MVC 的 HandlerAdapter 体系是工程级别适配器模式的教科书案例,值得深入研究:通过 supports() 方法实现多适配器的动态选择,通过统一的 handle() 接口抹平不同 Handler 的差异,让 DispatcherServlet 对 Handler 的多样性完全透明。
在实际工程中应用适配器模式时,要特别注意:
- 适配器不只是接口转换,还要做好异常转换和数据模型映射。
- 多适配器并存时要管理好优先级顺序。
- 性能敏感场景要关注数据转换的开销。
