RestTemplate 和 Feign 传参差异导致的接口调用失败
问题背景
项目中有一个试驾结束后推送 TDA 的功能,原来使用 RestTemplate 调用正常,后来改用 Feign 重构重推功能时,发现调用失败。
问题现象
原始代码(正常)
            
            
              java
              
              
            
          
          // 构建请求
HttpEntity<Object> adaptTDA = adapterTDARequest.stopTestDriveSendTda(
    getTestDriveSheetResult,
    queryUscMdmEmpResult.get(0)
);
// 使用 RestTemplate 调用
String tdaResult = postData(lookUpResult.get(0).getLookUpValueName(), adaptTDA);
        重构代码(失败)
            
            
              java
              
              
            
          
          // 构建请求(同样的方法)
HttpEntity<Object> adaptTDA = adapterTDARequest.stopTestDriveSendTda(
    sheetBO,
    queryUscMdmEmpResult.get(0)
);
// 改用 Feign 调用
tdaResult = tdaFeign.stopTestDriveSendTda(adaptTDA);
        Feign 接口定义
            
            
              java
              
              
            
          
          @FeignClient(name = "${feign.client.config.tda.url}", 
             url = "${feign.client.config.tda.url}")
public interface TDAFeign {
    @PostMapping(value = "/sca/saletool/smart/drive", 
                 consumes = "application/json")
    String stopTestDriveSendTda(@RequestBody Object param);
}
        排查过程
1. 对比日志
开启 Feign 和 RestTemplate 的日志后,发现请求体不一样:
RestTemplate 发送的请求体:
            
            
              csharp
              
              
            
          
          Writing [{"reception_ed":1760322532648,"client_info":{"client_phone":"18085741555","client_name":"曾兴宇","client_id":"77bd2f6c7c0f4288a08129663a574fcb"},"user_id":"106903","drive_id":"SCSJSS85102251012003","reception_bg":1760259783000,"drive_info":{"drive_ed":1760320732649,"drive_route":[],"drive_car":"HX11","drive_bg":1760261585000}}] as "application/json;charset=UTF-8"
        Feign 发送的请求体:
            
            
              csharp
              
              
            
          
          Writing [<{"reception_ed":1760322332033,"client_info":{"client_phone":"18602994468","client_name":"武宇青老公","client_id":"03679b8750404f738b81006bcb4aa203"},"user_id":"110094","drive_id":"SCSJSS91001251009001","reception_bg":1759978703000,"drive_info":{"drive_ed":1760320532033,"drive_route":[],"drive_car":"HX11","drive_bg":1759980505000}},[Content-Type:"application/json; charset=UTF-8"]>] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@511a307e]
        注意 Feign 的请求体多了 <...,[Content-Type:"application/json; charset=UTF-8"]> 这部分。
2. 分析差异
RestTemplate 发送的是纯 JSON:
            
            
              json
              
              
            
          
          {"reception_ed":...,"client_info":{...}}
        Feign 发送的包含了 HttpEntity 的结构:
            
            
              json
              
              
            
          
          {
  "body": {"reception_ed":...,"client_info":{...}},
  "headers": {"Content-Type": ["application/json; charset=UTF-8"]}
}
        显然,Feign 把整个 HttpEntity 对象序列化了。
原因分析
HttpEntity 的结构
            
            
              java
              
              
            
          
          public class HttpEntity<T> {
    private final HttpHeaders headers;  // 请求头
    private final T body;               // 请求体
    
    public HttpHeaders getHeaders() { return headers; }
    public T getBody() { return body; }
}
        HttpEntity 是 Spring 提供的一个包装类,用于同时携带请求体和请求头。
RestTemplate 的处理方式
查看 RestTemplate 源码:
            
            
              java
              
              
            
          
          // RestTemplate.java
public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
        @Nullable HttpEntity<?> requestEntity, Class<T> responseType, ...) {
    
    RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
    // ...
}
protected <T> RequestCallback httpEntityCallback(@Nullable HttpEntity<?> requestEntity, Type responseType) {
    return new HttpEntityRequestCallback(requestEntity, responseType);
}
        关键在 HttpEntityRequestCallback 类:
            
            
              java
              
              
            
          
          private class HttpEntityRequestCallback extends AcceptHeaderRequestCallback {
    
    private final HttpEntity<?> requestEntity;
    
    @Override
    public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
        super.doWithRequest(httpRequest);
        
        // 提取 body
        Object requestBody = this.requestEntity.getBody();
        
        if (requestBody != null) {
            // 提取 headers
            HttpHeaders requestHeaders = this.requestEntity.getHeaders();
            
            // 设置 headers
            if (!requestHeaders.isEmpty()) {
                httpRequest.getHeaders().putAll(requestHeaders);
            }
            
            // 序列化 body(只序列化 body,不包含 headers)
            // 使用 HttpMessageConverter 写入
            writeWithMessageConverters(requestBody, ...);
        }
    }
}
        RestTemplate 的处理流程:
- 识别传入的是 
HttpEntity类型 - 调用 
getBody()提取请求体 - 调用 
getHeaders()提取请求头 - 将 headers 设置到 HTTP 请求头
 - 只序列化 body 部分
 
Feign 的处理方式
Feign 接口定义:
            
            
              java
              
              
            
          
          @PostMapping(consumes = "application/json")
String stopTestDriveSendTda(@RequestBody Object param);
        Feign 的序列化逻辑:
            
            
              java
              
              
            
          
          // SpringEncoder.java
public void encode(Object requestBody, Type bodyType, RequestTemplate template) {
    
    // Feign 不会特殊处理 HttpEntity
    // 直接把传入的对象当成普通 Java 对象序列化
    
    for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
        if (messageConverter.canWrite(requestBody.getClass(), contentType)) {
            // 序列化整个 requestBody
            messageConverter.write(requestBody, contentType, outputMessage);
            return;
        }
    }
}
        Feign 的处理流程:
- 接收到 
Object类型的参数(实际是HttpEntity) - 不识别这是 
HttpEntity,当成普通对象 - 使用 Jackson 序列化整个对象
 - 结果包含了 
body和headers两个字段 
序列化结果对比
传入的对象:
            
            
              java
              
              
            
          
          HttpEntity<Map<String, Object>> adaptTDA = new HttpEntity<>(body, headers);
        RestTemplate 序列化:
            
            
              java
              
              
            
          
          // 只序列化 body
{"reception_ed": 1760322532648, "client_info": {...}}
        Feign 序列化:
            
            
              java
              
              
            
          
          // 序列化整个 HttpEntity 对象
{
  "body": {"reception_ed": 1760322532648, "client_info": {...}},
  "headers": {"Content-Type": ["application/json; charset=UTF-8"]}
}
        为什么会这样?
RestTemplate 的设计:
exchange()方法的参数类型明确是HttpEntity<?>- 内部有专门的 
HttpEntityRequestCallback处理 - 知道如何提取 body 和 headers
 
Feign 的设计:
- 接口方法参数类型是 
@RequestBody Object - 是一个通用的序列化框架
 - 不知道传入的是 
HttpEntity,当成普通 POJO 处理 
总结
核心问题
RestTemplate 和 Feign 对 HttpEntity 的处理方式不同:
- RestTemplate 会自动提取 body 和 headers
 - Feign 会把整个 
HttpEntity当成普通对象序列化 
根本原因
- RestTemplate 的 
exchange()方法参数类型是HttpEntity<?>,有专门的处理逻辑 - Feign 的接口方法参数类型是 
@RequestBody Object,是通用序列化,不识别HttpEntity 
经验教训
- 不同的 HTTP 客户端对同一个对象的处理可能不同
 - 从 RestTemplate 迁移到 Feign 时,不能简单替换
 - 遇到接口调用问题,要对比实际发送的请求体
 - 理解框架的设计原理,而不是死记 API
 
注意事项
使用 Feign 时:
- 直接传业务对象,不要用 
HttpEntity包装 - 需要自定义 headers 时,使用 
@RequestHeader注解