
本文详解如何在 Spring REST 控制器中对 JSON 请求体绑定的自定义对象(如 Search)进行精准、可反馈的字段级校验,确保非法字段名、空值、类型不匹配等错误在反序列化后立即被捕获并返回标准化错误响应。
本文详解如何在 spring rest 控制器中对 json 请求体绑定的自定义对象(如 search)进行精准、可反馈的字段级校验,确保非法字段名、空值、类型不匹配等错误在反序列化后立即被捕获并返回标准化错误响应。
在 Spring Boot 中,仅依赖 @Valid 并不能自动检测 JSON 中的字段拼写错误(如 "seletc" 误写)或未知字段——因为 Jackson 默认会静默忽略不存在的字段,导致对应 Java 字段为 null 或默认值,而校验逻辑(如 @NotEmpty)才真正触发。因此,校验需分两层协同工作:Jackson 的严格反序列化 + Bean 的约束验证。
✅ 正确做法:组合 Jackson 严格模式与 JSR-303 校验
1. 启用 Jackson 严格反序列化(捕获未知/错别字段)
在 application.yml 中配置:
spring:
jackson:
deserialization:
fail-on-unknown-properties: true # 遇到 JSON 中不存在的字段(如 "seletc")直接抛出 HttpMessageNotReadableException
fail-on-null-for-primitives: true
⚠️ 注意:此配置全局生效,确保所有控制器统一行为;若需局部控制,可通过
@JsonCreator+@JsonProperty手动构造函数实现更细粒度校验。
2. 完善 Search 类的校验注解(捕获空集合、非法元素等)
public class Search {
@NotEmpty(message = "select 字段不能为空列表")
private List<string> select = new ArrayList();
@NotEmpty(message = "filters 字段不能为空列表")
@Valid // 若 Filter 类自身含 @NotNull/@Size 等注解,必须加此注解触发递归校验
private List<filter> filters = new ArrayList();
// 建议提供无参构造器(Jackson 反序列化必需)
public Search() {}
public Search(List<string> select, List<filter> filters) {
this.select = select != null ? select : new ArrayList();
this.filters = filters != null ? filters : new ArrayList();
}
// getter/setter 省略
}</filter></string></filter></string>
✅ 关键点:
- 移除
@Valid在List<string></string>上的冗余使用(String是不可变基础类型,无需递归校验);@Valid保留在List<filter></filter>上,确保每个Filter实例内部校验生效;@NotEmpty比@NotNull更精确:它同时校验null和空集合(size()==0)。
3. 控制器方法声明(启用全局校验拦截)
@RestController
public class SearchController {
@PostMapping("/results")
public ResponseEntity> results(
@NotBlank(message = "id 不能为空字符串") String id,
@Valid @RequestBody Search search) { // ✅ @RequestBody 显式声明 JSON 绑定来源
// 业务逻辑
return ResponseEntity.ok("success");
}
}
? 说明:
@Valid必须与@RequestBody配合使用,才能触发MethodValidationPostProcessor的校验链。
4. 全局异常处理(返回友好错误响应)
@ControllerAdvice
public class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<map string>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<string string> errors = new HashMap();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.badRequest().body(errors);
}
// 处理 Jackson 反序列化失败(如未知字段)
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<map string>> handleJsonParseException(
HttpMessageNotReadableException ex) {
Map<string string> error = new HashMap();
error.put("error", "JSON 解析失败:请检查字段名是否正确(如 'select' 而非 'seletc'),并确保符合 API 文档结构");
return ResponseEntity.badRequest().body(error);
}
}</string></map></string></map>
✅ 最终效果
- 当客户端发送
{"seletc":["id"],"filters":[]}→ 触发HttpMessageNotReadableException→ 返回明确提示“字段名错误”; - 当发送
{"select":[],"filters":[]}→ 触发MethodArgumentNotValidException→ 返回{"select":"select 字段不能为空列表"}; - 所有校验失败均以
400 Bad Request响应,避免业务逻辑执行,提升 API 可靠性与调试效率。
? 最佳实践总结:
- Jackson
fail-on-unknown-properties=true是防御字段拼写错误的第一道防线;@Valid+@NotEmpty/@NotNull等注解是第二道防线,保障业务语义完整性;- 全局
@ControllerAdvice统一错误格式,让前端能精准定位问题。











