SpringMVC中@RequestBody引起的400异常处理,返回校验失败具体信息

本文介绍如何处理Spring中@RequestBody在解析JSON数据时遇到的问题。当接收到不符合条件的JSON数据时,Spring会返回400错误,但不会提供具体原因。文章提供了通过捕获HttpMessageNotReadableException来返回详细错误信息的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题

使用@RequestBody接收一个json数据的时候,如果传入的参数不符合条件,就会直接返回400的error page. 但究竟是为什么会400并没有抛出来。这对大量参数字段的我们来说,排错很困难,所以应该返回出错的原因。

Resolve

This was raised as an issue with Spring SPR-7439 - JSON (jackson) @RequestBody marshalling throws awkward exception - which was fixed in Spring 3.1M2 by having Spring throw a org.springframework.http.converter.HttpMessageNotReadableException in the case of a missing or invalid message body.

以上来自stackoverflow.

具体就是当spring的@RequestBody注解进行marshall操作的时候,如果jackson跑出异常,就会抛出一个叫做HttpMessageNotReadableException的异常,那么我们只要捕获并返回这个异常就可以了。

具体做法

最简单是在controller里加一个错误处理的方案:

@ExceptionHandler({HttpMessageNotReadableException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public CodeMsg resolveException(HttpMessageNotReadableException errors) {

    return CodeMsg.error(errors.getMessage());
}

如果想要对所有的controller生效,那么需要一个注解@ControllerAdvice:


@ControllerAdvice
public class GeneralHandler {

   @ExceptionHandler
   public ModelAndView handleException (NoSuchRequestHandlingMethodException ex) {
        ModelAndView mav = new ModelAndView();
        ...
        return mav;
   }
}

所以,可以处理你想要的异常:


@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public RestError resolveBindingException ( MethodArgumentNotValidException methodArgumentNotValidException, Locale locale )
{
    BindingResult bindingResult = methodArgumentNotValidException.getBindingResult();
    return getRestError(bindingResult, locale);
}




唯有不断学习方能改变! -- Ryan Miao
<think>我们正在讨论的是在FeignClient中使用HttpEntity作为参数的问题。根据之前的讨论,有两种方案:拆分参数和自定义编码器。现在用户提出了一个具体的方法定义:`RequestEntity<String>erpCallBack(@RequestBodyHttpEntity<String>requestBody)`,并询问其正确性及注意事项。首先,我们需要明确:在FeignClient的接口方法中,直接使用`@RequestBodyHttpEntity<String>`作为参数是符合Feign默认行为规范的。原因如下:1.**Feign的默认处理机制**:Feign默认使用`SpringMvcContract`(如果使用了SpringCloudOpenFeign)来解析注解。它支持SpringMVC的注解,如`@RequestMapping`,`@RequestBody`等。但是,`HttpEntity`是一个特殊的类型,它既包含请求头又包含请求体。Feign默认的编码器(Encoder)会处理`HttpEntity`类型,而是将整个`HttpEntity`对象当作请求体进行序列化,这会导致请求头丢失,并且请求体变成了一个序列化的`HttpEntity`对象,而是我们期望的请求体内容。2.**`@RequestBody`注解的用途**:根据引用[1]和引用[2],`@RequestBody`用于将HTTP请求体绑定到方法参数上。它通常用于绑定JSON、XML等格式的数据到Java对象。但是,当参数类型是`HttpEntity`时,SpringMVC在服务端可以处理,因为SpringMVC有相应的处理机制。然而,在Feign客户端,情况同,因为Feign客户端是一个HTTP客户端,它需要将方法参数转换为HTTP请求。3.**具体问题**:在方法`erpCallBack`中,我们使用`@RequestBodyHttpEntity<String>requestBody`。这样定义,Feign会尝试将整个`HttpEntity`对象作为请求体发送,而会提取其中的请求头和请求体分别设置。这会导致:-请求头信息丢失,因为整个`HttpEntity`对象被当成了请求体。-接收方(服务端)期望的请求体是一个字符串,但实际上收到的是一个序列化的`HttpEntity`对象(例如JSON格式),这会导致反序列化失败。因此,上述定义在FeignClient中是正确的。###解决方案####方案一:拆分参数(推荐)将`HttpEntity`拆分成请求头和请求体两个参数:```java@FeignClient(name="erp-service")publicinterfaceErpFeignClient{@PostMapping("/callback")RequestEntity<String>erpCallBack(@RequestHeaderHttpHeadersheaders,//使用HttpHeaders传递所有请求头@RequestBodyStringbody//请求体为字符串);}```调用时:```javaHttpHeadersheaders=newHttpHeaders();headers.set("Authorization","Bearertoken");StringrequestBody="jsonstringorplaintext";RequestEntity<String>response=erpFeignClient.erpCallBack(headers,requestBody);```**优点**:-清晰明了,符合Feign的设计。-无需额外配置。####方案二:自定义编码器(高级)如果坚持要在FeignClient接口中使用`HttpEntity`作为参数,则需要自定义编码器,如之前所述。但请注意,这种方法需要谨慎处理,因为可能会影响其他Feign接口。1.定义自定义编码器(如前所述)。2.配置FeignClient使用该编码器。然后可以定义:```java@FeignClient(name="erp-service",configuration=FeignConfig.class)publicinterfaceErpFeignClient{@PostMapping("/callback")RequestEntity<String>erpCallBack(HttpEntity<String>entity);//注意:这里能加@RequestBody注解}```**重要**:在方法参数上,我们应该使用`@RequestBody`注解,因为自定义编码器会处理整个`HttpEntity`对象,包括头和体。如果加上`@RequestBody`,Feign会试图将`HttpEntity`作为请求体再次处理,导致问题。###注意事项1.**服务端匹配**:服务端接口需要能够接收`HttpEntity`作为参数。服务端可能是这样定义的:```java@PostMapping("/callback")publicResponseEntity<String>callback(HttpEntity<String>entity){//从entity中获取头信息和请求体}```或者使用`@RequestBody`和`@RequestHeader`分开接收。2.**请求体类型**:在方案一中,我们使用`@RequestBodyStringbody`,这意味着请求体是一个字符串。如果请求体是JSON,并且你希望映射为对象,那么可以将`String`替换为具体的对象类型(如`MyDTO`)。同样,服务端也应该使用`@RequestBody`来接收这个对象。3.**请求头传递**:使用`@RequestHeaderHttpHeadersheaders`可以传递多个请求头。注意,`HttpHeaders`是一个`MultiValueMap`,可以包含多个值。Feign会将其中的每个键值对设置为请求头。4.**避免使用`@RequestBody`注解在`HttpEntity`参数上**:在方案二中,我们移除了`@RequestBody`注解,因为自定义编码器会处理整个`HttpEntity`。如果保留`@RequestBody`,编码器会尝试将`HttpEntity`对象序列化为请求体,这是我们想要的。5.**性能考虑**:自定义编码器可能会增加序列化的复杂性,尤其是在处理多种类型参数时。因此,除非有充分的理由(例如多个接口都需要使用`HttpEntity`),否则推荐使用方案一。###总结用户提出的方法定义`RequestEntity<String>erpCallBack(@RequestBodyHttpEntity<String>requestBody)`在FeignClient中是正确的,因为它会导致整个`HttpEntity`对象被当作请求体发送。推荐将参数拆分为请求头和请求体两部分,或者使用自定义编码器(并移除`@RequestBody`注解)。###相关问题1.FeignClient如何传递文件(MultipartFile)?2.如何在FeignClient中统一添加认证头信息?3.FeignClient调用时如何设置超时时间?4.如何为FeignClient配置HTTP客户端(如ApacheHttpClient、OKHttp)?[^1]:`@RequestBody`注解的作用[^2]:`@RequestBody`注解的使用场景</think>### FeignClient 使用 `HttpEntity` 作为 `@RequestBody` 参数的正确性分析 您的方法定义 `RequestEntity<String> erpCallBack(@RequestBody HttpEntity<String> requestBody)` 在 FeignClient 中存在**设计问题**,具体原因如下: #### ❌ 问题核心 1. **参数类型冲突**: - `@RequestBody` 注解的用途是将 HTTP 请求体绑定到单个对象(如 DTO 或基本类型)[^1][^2] - `HttpEntity` 是一个复合对象(包含 headers + body),Feign 无法将其视为纯请求体处理 - 实际调用时,Feign 会尝试将整个 `HttpEntity` 对象序列化为请求体,导致: ```json // 错误序列化结果示例 { "headers": { "Content-Type": "text/plain" }, "body": "实际内容", "class": "org.springframework.http.HttpEntity" } ``` 2. **请求头丢失**: - `HttpEntity` 中的 headers 信息会被完全忽略 - 服务端无法接收到原始的头信息(如 `Content-Type`, `Authorization` 等) --- ### ✅ 正确实现方案 #### 方案一:拆分参数(推荐) ```java @FeignClient(name = "erp-service") public interface ErpFeignClient { @PostMapping("/callback") RequestEntity<String> erpCallBack( @RequestHeader HttpHeaders headers, // 显式声明头参数 @RequestBody String rawBody // 直接声明请求体 ); } // 调用示例 public void callErp() { HttpHeaders headers = new HttpHeaders(); headers.add("X-Auth-Token", "token_value"); headers.setContentType(MediaType.APPLICATION_JSON); String jsonBody = "{\"data\":\"value\"}"; RequestEntity<String> response = erpFeignClient.erpCallBack(headers, jsonBody); } ``` **优势**: - 100% 兼容 Feign 原生机制 - 明确分离头信息和请求体 - 支持完整的类型安全校验 #### 方案二:自定义编码器(需配置) ```java @Configuration public class FeignConfig { @Bean public Encoder feignEncoder() { return new HttpEntityEncoder(new SpringEncoder(messageConverters)); } } @FeignClient(name = "erp-service", configuration = FeignConfig.class) public interface ErpFeignClient { // 注意:此处能使用 @RequestBody @PostMapping("/callback") RequestEntity<String> erpCallBack(HttpEntity<String> entity); } // 调用示例 HttpEntity<String> entity = new HttpEntity<>( "请求体内容", new HttpHeaders(){{ add("Header", "value"); }} ); RequestEntity<String> response = erpFeignClient.erpCallBack(entity); ``` **关键点**: 1. **必须移除 `@RequestBody` 注解** `HttpEntity` 本身已包含元数据 2. 需配置自定义编码器(参考前文示例) --- ### ⚠️ 重要注意事项 1. **服务端兼容性**: - 服务端接口应使用 `HttpEntity` 接收完整请求: ```java @PostMapping("/callback") public ResponseEntity<?> handleCallback(HttpEntity<String> entity) { String body = entity.getBody(); HttpHeaders headers = entity.getHeaders(); } ``` 2. **内容类型处理**: - 若请求体是 JSON 对象,推荐使用 DTO 代替 `String`: ```java // 客户端 @RequestBody ErpRequestDTO body // 服务端 @RequestBody ErpRequestDTO dto ``` 3. **性能影响**: - 自定义编码器会增加序列化开销 - 高频调用场景推荐方案一 4. **错误实践**: ```java // 绝对避免的写法 @PostMapping void wrongMethod(@RequestBody HttpEntity<?> entity) ``` 这种写法会导致: - 请求体被错误序列化 - 服务端收到非法负载 --- ### 总结建议 | 方案 | 适用场景 | 风险等级 | |---------------|---------------------------------|----------| | 拆分参数 | 标准业务场景 | ★☆☆☆☆ | | 自定义编码器 | 需要完全兼容 Spring `HttpEntity` | ★★★☆☆ | **首选方案一**,它在维护性、性能、兼容性上均优于直接使用 `HttpEntity` + `@RequestBody` 的组合[^1][^2]。 --- ### 相关问题 1. 如何在 FeignClient 中统一添加全局请求头? 2. FeignClient 调用时如何传递 `MultipartFile` 文件? 3. 如何为 FeignClient 配置自定义错误解码器? 4. 服务端接收 `HttpEntity` 和 `@RequestBody` 有什么区别?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值