SpringBoot--SpringBoot参数校验与类型转换异常

前言:

1、 常用做法

  1. Controller 层参数类型直接用 Java 类型(Integer、Long 等) → Spring 自带类型转换器会拦截非数字字符串。

  2. 加上 JSR-303 参数校验@Valid + @Min 等)→ 保证规则正确。

  3. 数据库字段用合适类型(避免用 VARCHAR 存数字)。

这样,即使前端传错类型,错误会:

  • 第一道防线:Spring 类型绑定失败 → 400 错误。

  • 第二道防线:参数校验失败 → 自定义提示。

  • 第三道防线:数据库类型不匹配 → SQL 报错。

2、@Valid / @Validated 参数校验注解何时生效

(1)如果类型不匹配则不需要参数校验,springboot就会直接抛出异常

类型不匹配 本质上是 Spring 在绑定参数(数据类型转换)阶段 就失败了,这一步发生在校验(@Valid / @Validated)之前

所以:

  • 就算你没加 @Validated,只要类型对不上,Spring 也会直接抛异常(比如 MethodArgumentTypeMismatchExceptionHttpMessageNotReadableException)。

  • @Validated(或 @Valid)只是触发 Bean Validation (如 @NotNull@Min)的规则检查,它是在类型绑定成功之后才执行的。


**(2)**参数校验触发顺序

  1. 类型绑定阶段(Spring MVC 把字符串、JSON等 转成 Java 类型)

    • 如果转换失败(类型不匹配),直接抛异常,不会进入校验阶段

    • 对应异常:

      • JSON → Java 对象失败 → HttpMessageNotReadableException

      • URL 参数绑定失败 → MethodArgumentTypeMismatchException

  2. Bean Validation 校验阶段(@Valid / @Validated)

    • 只会在 类型绑定成功后才执行

    • 触发异常:

      • @RequestBody Bean 校验失败 → MethodArgumentNotValidException

      • 方法参数校验失败 → ConstraintViolationException


**(3)**举例

java 复制代码
@GetMapping("/test")
public String test(@RequestParam @Min(0) Integer age) {
    return "ok";
}
请求 结果 原因
/test?age=abc MethodArgumentTypeMismatchException 绑定失败,根本没到校验
/test?age=-1 ConstraintViolationException 绑定成功,校验失败(@Min 触发)

所以总结:

  • 类型不匹配 → 不需要 @Validated,绑定阶段就抛错

  • 规则不符合 (值合法性) → 需要 @Valid / @Validated 触发 Bean Validation

参数校验详解(JSR-303 + Hibernate Validator,Spring Boot 常用实践)

一、为什么用 JSR-303(Hibernate Validator)

  • JSR-303(现在规格名通常叫 Bean Validation / Jakarta Validation)定义了注解式校验规范,Hibernate Validator 是它的参考实现。Spring Boot 推荐用 spring-boot-starter-validation 快速引入。

二、请求处理的"先后顺序"(非常关键)

  1. HTTP 请求体(JSON) → 先由 Jackson 把 JSON 反序列化为 DTO(POJO)。

    • 如果 JSON 字段类型不合(比如 DTO 中 Integer age,JSON 里 "age":"abc"),Jackson 会抛出反序列化异常InvalidFormatException 等),这会被 Spring 包装成 HttpMessageNotReadableException,并返回 400。这一步发生在 Bean 验证之前 。所以 @Valid 根本还没跑到。
  2. 反序列化成功后 ,Spring 对 DTO 执行 Bean Validation(JSR-303) ,也就是 @NotNull@Min 等注解生效,会抛 MethodArgumentNotValidException(用于 @RequestBody 场景)。Baeldung on Kotlin

  3. 方法级参数校验 (如 @RequestParam@PathVariable、方法参数上的注解)需要配合 @Validated 启用,失败会抛 ConstraintViolationExceptionHomeBaeldung on Kotlin

所以核心结论:类型转换(Jackson)错误 ≠ 校验失败,两者需要分别处理才不会出现"看起来没生效"的情况。


三、常见注解(选你常用的)

  • @NotNull:不能为 null(适合对象、包装类型)。

  • @NotBlank:字符串非 null 且有非空白字符。

  • @NotEmpty:集合/字符串非空。

  • @Min/@Max:数值最小/最大。

  • @Positive/@PositiveOrZero:正数 / 非负数。

  • @Size(min=, max=):字符串/集合长度。

  • @Pattern:正则。

  • @Email@Past@Future@Digits 等。

(更多及差异可参考 Hibernate Validator 文档。)


四、最典型的代码示例(完整流程)

4.1 依赖(Maven)

XML 复制代码
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

4.2 DTO

java 复制代码
import jakarta.validation.constraints.*;

public class UserDTO {
    @NotNull(message = "年龄不能为空")
    @Min(value = 0, message = "年龄不能小于0")
    private Integer age;

    @NotBlank(message = "姓名不能为空")
    private String name;

    // getters/setters
}

4.3 Controller(@RequestBody + 校验)

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("/add")
    public ResponseEntity<String> addUser(@Valid @RequestBody UserDTO user) {
        // 到这里说明反序列化成功且校验通过
        return ResponseEntity.ok("ok");
    }
}

4.4 Controller(method-param 验证:@RequestParam / @PathVariable)

java 复制代码
@RestController
@Validated          // 必须有这个,才能对方法参数上的注解生效
public class DemoController {

    @GetMapping("/age")
    public String getByAge(@RequestParam @Min(0) Integer age) {
        return "age=" + age;
    }
}

如果 ?age=abc 会在数据绑定阶段报类型转换错误(MethodArgumentTypeMismatchException / TypeMismatchException),需单独处理;如果 ?age=-1 则通过参数校验会抛出 ConstraintViolationException


五、统一异常处理(推荐)------把 400 错误给前端友好化

下面的 @RestControllerAdvice 覆盖常见场景:反序列化失败、DTO 校验失败、方法参数校验失败、类型不匹配。

java 复制代码
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 1) @RequestBody 的校验失败(@Valid)-> BindingResult -> MethodArgumentNotValidException
     // 触发条件:
         //发生在 @RequestBody + @Valid/@Validated 校验 Java Bean 的场景。
         //前端传的 JSON 能成功反序列化为 Java 对象,但字段值不符合校验注解的规则(@NotNull、@Min 等)。
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors()
          .forEach(error -> errors.put(error.getField(), error.getDefaultMessage()));
        return ResponseEntity.badRequest().body(errors);
    }

    // 2) JSON 反序列化 / 类型转换(例如 age: "abc" -> Integer)导致的异常
     // 触发条件
        //发生在 反序列化 JSON 到 Java 对象时失败,通常是字段类型不匹配或 JSON 格式错误。
        //Spring 在还没进入参数校验阶段就失败了(对象根本没创建出来)。
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<String> handleJsonParse(HttpMessageNotReadableException ex) {
        Throwable cause = ex.getCause();
        if (cause instanceof InvalidFormatException) {
            InvalidFormatException ife = (InvalidFormatException) cause;
            String path = ife.getPathReference(); // like [Field 'age']
            String targetType = ife.getTargetType().getSimpleName();
            return ResponseEntity.badRequest().body(path + " 无法转换为 " + targetType);
        }
        return ResponseEntity.badRequest().body("JSON 解析错误");
    }

    // 3) URL 参数类型不匹配(?age=abc 绑定到 Integer)或 PathVariable 类型不匹配
     // 触发条件
        //发生在 URL 查询参数 / PathVariable 类型转换失败(即非 JSON 场景)。
        //Spring MVC 在绑定参数时,类型不匹配直接抛这个异常。
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseEntity<String> handleTypeMismatch(MethodArgumentTypeMismatchException ex) {
        return ResponseEntity.badRequest().body(
            "参数 " + ex.getName() + " 的类型应该是 " + ex.getRequiredType().getSimpleName()
        );
    }

    // 4) 方法级参数(@Validated)触发的 ConstraintViolationException(例如 @RequestParam @Min)
     // 触发条件
        //发生在 方法级别参数校验失败(@Validated + @RequestParam / @PathVariable / @ModelAttribute 上的约束注解)。
        //必须类上有 @Validated 才会触发方法级参数校验。
    @ExceptionHandler(javax.validation.ConstraintViolationException.class)
    public ResponseEntity<Map<String, String>> handleConstraintViolation(javax.validation.ConstraintViolationException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getConstraintViolations().forEach(cv -> {
            String path = cv.getPropertyPath().toString();
            errors.put(path, cv.getMessage());
        });
        return ResponseEntity.badRequest().body(errors);
    }
}

这套处理能把**类型转换错误("abc")校验失败(-1 / 不满足注解)**分开并返回可读信息。参考了常见的最佳实践。

5.1区别例子

1️⃣HttpMessageNotReadableException

触发时机

  • 场景@RequestBody 接收 JSON

  • 阶段 :Spring 把请求体(JSON) → Java 对象的 反序列化阶段(Jackson)

  • 原因:JSON 格式错误、字段类型对不上

例子

java 复制代码
@PostMapping("/user")
public void addUser(@RequestBody UserDTO dto) { }

public class UserDTO {
    private Integer age;
}

请求:

bash 复制代码
{"age": "abc"}
  • Spring 读取 JSON 发现 "abc" 不能转成 Integer

  • 还没到参数校验阶段 ,直接在 Jackson 解析时抛 HttpMessageNotReadableException

📌 关键词@RequestBody + JSON + 类型不匹配/格式错


2️⃣MethodArgumentTypeMismatchException

触发时机

  • 场景@RequestParam / @PathVariable / @ModelAttribute

  • 阶段:Spring 把 URL 参数绑定到方法参数时(不是 JSON,是 query/path/form)

  • 原因:URL 上的值不能转换为目标类型

例子

java 复制代码
@GetMapping("/age")
public String getAge(@RequestParam Integer age) { return "..."; }

请求:

bash 复制代码
GET /age?age=abc
  • Spring 拿到 "abc" 要转成 Integer

  • 转换失败 → 抛 MethodArgumentTypeMismatchException

📌 关键词:URL 参数绑定 + 类型不匹配


3️⃣ 核心区别表
特性 HttpMessageNotReadableException MethodArgumentTypeMismatchException
数据来源 JSON 请求体 (@RequestBody) URL 参数 / Path 变量
解析阶段 JSON 反序列化(Jackson) 参数绑定(Spring MVC 类型转换器)
常见触发 JSON 语法错 / 字段类型不对 URL 参数值类型不对
示例 {"age": "abc"} → Integer /age?age=abc → Integer

4️⃣ 记忆口诀

JSON 进来的错 = HttpMessageNotReadableException
URL 上的错 = MethodArgumentTypeMismatchException

5.2总结成对照表

异常类型 典型触发场景 数据来源 数据问题
MethodArgumentNotValidException @RequestBody + @Valid/@Validated 校验 Java Bean JSON 反序列化成功,但字段值不合法
HttpMessageNotReadableException @RequestBody JSON 转 Java Bean 失败 JSON JSON 结构错误或字段类型不匹配
MethodArgumentTypeMismatchException @RequestParam / @PathVariable 类型转换失败 URL 参数 值不能转换成目标类型
ConstraintViolationException 方法参数级别校验失败 URL 参数/PathVariable/Form 值不符合校验规则(@Min 等)

六、@Valid与@Validated区别,以及何时用哪个?

6.1 二者区别

(6.1.1)DTO + @Valid(针对对象字段校验)
java 复制代码
@PostMapping("/addUser")
public void addUser(@Valid @RequestBody UserDTO user) {
    // ...
}
  • 校验对象UserDTO 里的字段(agename 等)。

  • 触发条件 :参数是一个 Java Bean(DTO),@RequestBody 场景下需要加 @Valid 才会对这个对象的每个字段进行 JSR-303 校验。

  • 失败异常MethodArgumentNotValidException(适用于 JSON 反序列化成功后的 Bean 校验)。

  • 作用范围:只能校验这个对象的字段,不会自动校验单独的方法参数。

适合前端一次传多个字段的场景(比如注册、修改资料)。


(6.1.2) 方法参数 + @Validated(针对单个参数校验)

注意:Validated要加在@RequestParam前不行,必须加在类上,但是可以加在 @RequestBody前面,也就是下面**" ②何时用哪个?"中说的**

java 复制代码
@RestController
@Validated // 必须有这个才能生效
public class DemoController {

    @GetMapping("/age")
    public String getByAge(@RequestParam @Min(0) Integer age) {
        return "age=" + age;
    }
}
  • 校验对象 :单个方法参数(age)。

  • 触发条件 :类上或方法上有 @Validated,Spring 才会在调用方法前对每个参数执行 JSR-303 校验。

  • 失败异常ConstraintViolationException(方法参数校验失败时抛出)。

  • 作用范围:适合 GET 请求、查询参数、路径参数等"单字段"校验。

适合这种接口:GET /age?age=10,不需要传整个 JSON 对象。

6.2 何时用哪个?

(6.2.1)Bean 校验(两者都行)
java 复制代码
@PostMapping("/addUser")
public void addUser(@Valid @RequestBody UserDTO user) { ... }

// 或
@PostMapping("/addUser")
public void addUser(@Validated @RequestBody UserDTO user) { ... }

这里 @Valid 和 @Validated 等效,随便哪个都行。


(6.2.2)方法参数校验(只能用 @Validated而且要加载类上)
java 复制代码
@RestController
@Validated // 必须有这个
public class DemoController {
    @GetMapping("/age")
    public String getByAge(@RequestParam @Min(0) Integer age) {
        return "age=" + age;
    }
}

这里如果用 @Valid@Min 是不会生效的。


(6.2.3)选用建议
  • 对象字段校验 → 用哪个都行(习惯上 @Valid 更常见,因为它是 JSR 标准)。

  • 方法参数校验 → 用 @Validated

  • 嵌套对象 → 在嵌套字段上用 @Valid


(6.2.4)核心区别对照表
场景 触发注解 校验目标 适用参数类型 常见异常 使用场景
DTO 对象校验 @Valid Bean 内部字段 @RequestBody 接收的 Java Bean MethodArgumentNotValidException JSON 传对象,批量字段校验
方法参数校验 @Validated 单个方法参数 @RequestParam@PathVariable@RequestHeader ConstraintViolationException GET 请求、路径参数、单字段校验

七、常见问题与小结(快速答疑)

  • 前端传 "age":"abc"@Valid 为什么没生效?

    因为 Jackson 在反序列化阶段就失败了(类型不匹配),Bean 验证无机会运行。需要在 HttpMessageNotReadableException 中处理并返回友好提示。Baeldung on Kotlin

  • 想让 @RequestParam 也校验怎么办?

    在 Controller 类或方法上加 @Validated,并在参数上写注解(如 @Min)。失败会抛 ConstraintViolationExceptionHome

  • 如何给校验信息国际化/自定义消息?

    ValidationMessages.propertiesmessages.properties 中定义键,然后在注解中写 message = "{你的键}"。(官方与社区都有示例。)

  • 企业最佳实践(推荐)

    1. 使用 DTO + 注解(JSR-303)做规则校验;

    2. 统一 @RestControllerAdvice 处理各种异常(反序列化、校验、类型不匹配);

    3. 把业务逻辑假设的"合法输入"放在校验层拦截,保证 service 层收到的都是干净数据。

相关推荐
笑衬人心。7 分钟前
缓存的三大问题分析与解决
java·spring·缓存
XiangCoder28 分钟前
🔥Java核心难点:对象引用为什么让90%的初学者栽跟头?
后端
二闹39 分钟前
LambdaQueryWrapper VS QueryWrapper:安全之选与灵活之刃
后端
得物技术39 分钟前
Rust 性能提升“最后一公里”:详解 Profiling 瓶颈定位与优化|得物技术
后端·rust
用户849137175471641 分钟前
JDK 17 实战系列(第7期):迁移指南与最佳实践
java·jvm
XiangCoder44 分钟前
Java编程案例:从数字翻转到成绩统计的实用技巧
后端
duration~1 小时前
SpringAI实现Reread(Advisor)
java·人工智能·spring boot·spring
aiopencode1 小时前
iOS 文件管理全流程实战,从开发调试到数据迁移
后端
我们从未走散1 小时前
面试题-----微服务业务
java·开发语言·微服务·架构
YuforiaCode1 小时前
24SpringCloud黑马商城微服务整合Seata重启服务报错的解决办法
java·spring·微服务