spring-boot-starter-validation使用注解进行参数校验

目录

  • 前言
  • [一、maven 依赖](#一、maven 依赖)
  • 二、全局异常处理
  • [三、@Valid / @Validated](#三、@Valid / @Validated)
    • 1.@Valid(最常用)
    • [2.@Validated(Spring 提供,增强版)](#2.@Validated(Spring 提供,增强版))
  • 四、常见校验注解
    • [1. @NotNull(最基础)](#1. @NotNull(最基础))
    • [2. @NotEmpty(比 NotNull 严格一点)](#2. @NotEmpty(比 NotNull 严格一点))
    • [3. @NotBlank(字符串专用,最常用)](#3. @NotBlank(字符串专用,最常用))
    • [4. @Length(字符串长度,Hibernate 扩展)](#4. @Length(字符串长度,Hibernate 扩展))
    • [5. @Size(通用长度校验)](#5. @Size(通用长度校验))
    • [6. @Min / @Max(数值范围)](#6. @Min / @Max(数值范围))
    • [7. @Positive / @PositiveOrZero(推荐)](#7. @Positive / @PositiveOrZero(推荐))
    • [8. @Email(邮箱校验)](#8. @Email(邮箱校验))
    • [9. @Pattern(正则校验,强力)](#9. @Pattern(正则校验,强力))
    • [10. @AssertTrue / @AssertFalse](#10. @AssertTrue / @AssertFalse)
  • 五、测试
    • [1.post 请求](#1.post 请求)
    • [2.get 请求](#2.get 请求)
    • 3.分组校验

前言

在 Spring Boot 开发中,数据校验(Bean Validation)是保证系统健壮性的第一道防线。它能让你告别满屏的 if (user == null),让代码更优雅、更具可读性。

我们通常使用的这些注解大多来自 Jakarta Bean Validation(原 JSR 303/349/380)规范及其参考实现 Hibernate Validator。

一、maven 依赖

对于 Spring Boot 2.x :

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

对于 Spring Boot 3.x(Jakarta 包名):

依赖不变,但代码里引用的包名发生变化:javax.validation.* → jakarta.validation.*

如果没引入这个依赖:@Valid 不生效,校验注解写了等于没写

二、全局异常处理

全局异常处理用于参数校验失败以及业务异常时,将接口错误信息返回

1.定义通用返回类

java 复制代码
public class ApiResult<T> {

    private int code;
    private String message;
    private T data;

    public static <T> ApiResult<T> fail(String message, T data) {
        return new ApiResult<>(400, message, data);
    }

    public ApiResult() {}

    public ApiResult(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

2.全局异常处理类

java 复制代码
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RestControllerAdvice
public class GlobalExceptionHandler {
     /** RequestBody 校验失败 **/
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResult<List<Map<String, String>>> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
        List<Map<String, String>> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(e -> {
                    Map<String, String> m = new HashMap<>();
                    m.put("field", e.getField());
                    m.put("message", e.getDefaultMessage());
                    return m;
                })
                .collect(Collectors.toList());

        return ApiResult.fail("参数校验失败", errors);
    }

    /** RequestParam / PathVariable 校验失败 **/
    @ExceptionHandler(ConstraintViolationException.class)
    public ApiResult<List<Map<String, String>>> handleConstraintViolation(ConstraintViolationException ex) {
        List<Map<String, String>> errors = ex.getConstraintViolations()
                .stream()
                .map(v -> {
                    Map<String, String> m = new HashMap<>();
                    // propertyPath 形如:get.id / method.arg0
                    m.put("field", v.getPropertyPath().toString());
                    m.put("message", v.getMessage());
                    return m;
                })
                .collect(Collectors.toList());

        return ApiResult.fail("参数校验失败", errors);
    }
	
}

三、@Valid / @Validated

1.@Valid(最常用)

java 复制代码
import jakarta.validation.Valid;

作用:触发校验,本身不定义规则

常见位置:Controller 方法参数、成员变量(级联校验)

示例:

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

没有 @Valid,UserDTO 里的所有校验注解都不会执行

2.@Validated(Spring 提供,增强版)

java 复制代码
import org.springframework.validation.annotation.Validated;

特点:支持分组校验,可用在 类、方法上

示例:

java 复制代码
@Validated
@RestController
public class UserController {
}

90% 场景用 @Valid 即可

四、常见校验注解

1. @NotNull(最基础)

含义:

  • 不能为 null

  • 可以是空字符串 ""

适用类型:

  • 所有对象类型(Integer、Long、String、List...)
java 复制代码
@NotNull(message = "用户ID不能为空")
private Long userId;

2. @NotEmpty(比 NotNull 严格一点)

含义:

  • 不能为 null

  • 不能为 ""

  • 集合不能是空集合

适用类型:

  • String

  • Collection / Map / Array

java 复制代码
@NotEmpty
private String name;

@NotEmpty
private List<String> roles;

3. @NotBlank(字符串专用,最常用)

含义:

  • 不能为 null

  • 不能为 ""

  • 不能是 " "(空白)

适用类型:

  • 适用于 String
java 复制代码
@NotBlank(message = "用户名不能为空")
private String username;

4. @Length(字符串长度,Hibernate 扩展)

适用类型:

  • 只能用于 String
java 复制代码
import org.hibernate.validator.constraints.Length;

@Length(min = 6, max = 20, message = "长度必须在6-20位之间")
private String password;

5. @Size(通用长度校验)

java 复制代码
@Size(min = 1, max = 5)
private List<Long> ids;

适用类型:

  • String

  • Collection

  • Map

  • Array

与 @Length 区别:

注解 适用范围

@Length : String(Hibernate)

@Size : String + 集合(标准)

6. @Min / @Max(数值范围)

java 复制代码
@Min(1)
@Max(100)
private Integer age;

⚠️ 不能校验字符串

7. @Positive / @PositiveOrZero(推荐)

java 复制代码
@Positive        // > 0
@PositiveOrZero  // >= 0
java 复制代码
@Positive
private Long amount;

8. @Email(邮箱校验)

java 复制代码
@Email(message = "邮箱格式不正确")
private String email;

9. @Pattern(正则校验,强力)

java 复制代码
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式错误")
private String phone;

10. @AssertTrue / @AssertFalse

java 复制代码
@AssertTrue
private Boolean agree;

五、测试

java 复制代码
import org.hibernate.validator.constraints.Length;
import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import java.util.List;

public class Student {
    @NotBlank(message = "用户名不能为空")
    private String name;

    @NotBlank(message = "stuendId不能为空")
    @Length(min = 6, max = 20, message = "stuendId长度必须在6-20位之间")
    private String stuendId;

    @NotNull(message = "teacherName不能为null")
    private String teacherName;

    @Min(value = 18, message = "年龄必须大于18岁")
    private Integer age;

    @Email(message = "邮箱格式不正确")
    private String email;

    @Positive(message = "amount要大于0")
    private Long amount;

    @NotEmpty(message = "roles不能为空")
    private List<String> roles;

    /** 级联校验:如果要校验这个列表里的 Address 对象,必须加 @Valid **/
    @NotNull(message = "地址列表不能为空")
    @Valid
    private List<SendMessageModel> addresses;

	 //省略get、set 方法
}
java 复制代码
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;

public class SendMessageModel {
    @NotBlank(message = "mobile不能为空")
    String mobile;
    @Length(min = 6, max = 20, message = "sign长度必须在6-20位之间")
    String sign;
    @NotBlank(message = "content不能为空")
    String content;
    
	 //省略get、set 方法
}

1.post 请求

java 复制代码
import com.xiaohaitang.somedemo.exception.Student;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;

@RestController
@RequestMapping(value = "/apiList")
public class Usercontroller {

    @PostMapping(value = "/v1/addStudents")
    public String validChechaAddStudents(@RequestBody @Valid Student student) {
        return "success";
    }
}

入参:

java 复制代码
 {
	"age": 0,
	"email": "string",
	"amount": 0,
	"addresses": [{}]
}

响应:

java 复制代码
{
    "code": 400,
    "message": "参数校验失败",
    "data": [
        {
            "field": "teacherName",
            "message": "teacherName不能为null"
        },
        {
            "field": "email",
            "message": "邮箱格式不正确"
        },
        {
            "field": "addresses[0].mobile",
            "message": "mobile不能为空"
        },
        {
            "field": "amount",
            "message": "amount要大于0"
        },
        {
            "field": "age",
            "message": "年龄必须大于18岁"
        },
        {
            "field": "name",
            "message": "用户名不能为空"
        },
        {
            "field": "addresses[0].content",
            "message": "content不能为空"
        },
        {
            "field": "stuendId",
            "message": "stuendId不能为空"
        },
        {
            "field": "roles",
            "message": "roles不能为空"
        }
    ]
}

2.get 请求

必须在类上增加@Validated 参数里的校验才会生效

java 复制代码
import org.hibernate.validator.constraints.Length;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;

@Validated  //  必须:启用方法参数校验
@RestController
@RequestMapping(value = "/apiList")
public class Usercontroller {

    @GetMapping("/test")
    public String test(
            @RequestParam
            @NotBlank(message = "name 不能为空")
            @Length(min = 6, max = 20, message = "name长度必须在6-20位之间")
            String name,
            @RequestParam
            @NotNull(message = "id 不能为空")
            @Positive(message = "id 必须大于 0")
                    Long id
    ) {
        return "success";
    }
}

入参:

java 复制代码
http://localhost:8080/apiList/test?id=0&name=aaa

响应:

java 复制代码
{
    "code": 400,
    "message": "参数校验失败",
    "data": [
        {
            "field": "test.id",
            "message": "id 必须大于 0"
        },
        {
            "field": "test.name",
            "message": "name长度必须在6-20位之间"
        }
    ]
}

3.分组校验

对于同一个类,可能会用作不同接口的参数,这个时候可能就需要分组校验,让不同的接口指定不同的分组校验规则。

java 复制代码
public interface OnCreate {
}
java 复制代码
public interface OnUpdate {
}
java 复制代码
import com.xiaohaitang.somedemo.model.SendMessageModel;
import org.hibernate.validator.constraints.Length;

import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import java.util.List;

public class Student {
    @NotBlank(message = "用户名不能为空")
    private String name;
    
    @Length(groups = OnCreate.class, min = 6, max = 20, message = "stuendId长度必须在6-20位之间")
    @NotBlank(groups = OnUpdate.class, message = "stuendId不能为空")
    @NotNull(message = "stuendId不能为null")
    private String stuendId;

    @NotNull(message = "teacherName不能为null")
    private String teacherName;

    @Min(value = 18, message = "年龄必须大于18岁")
    private Integer age;

    @Email(message = "邮箱格式不正确")
    private String email;

    @Positive(message = "amount要大于0")
    private Long amount;

    @NotEmpty(message = "roles不能为空")
    private List<String> roles;

    /** 级联校验:如果要校验这个列表里的 Address 对象,必须加 @Valid **/
    @NotNull(message = "地址列表不能为空")
    @Valid
    private List<SendMessageModel> addresses;
    
	 //省略get、set 方法
}
java 复制代码
import com.xiaohaitang.somedemo.exception.OnCreate;
import com.xiaohaitang.somedemo.exception.OnUpdate;
import com.xiaohaitang.somedemo.exception.Student1;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;

@RestController
@RequestMapping(value = "/apiList")
public class Usercontroller {

		//使用 @Valid ,只会校验 Student 里含有 groups = OnCreate.class 规则
    @PostMapping(value = "/users")
    public String create(@RequestBody @Validated(OnCreate.class) Student student) {
        return "success";
    }

		//使用 @Valid ,只会校验 Student 里含有 groups = OnUpdate.class 规则
    @PutMapping(value = "/users")
    public String update(@RequestBody @Validated(OnUpdate.class) Student student) {
        return "success";
    }

		//使用 @Valid ,只会校验 Student 里的默认规则
    @PostMapping(value = "/otherDefult")
    public String otherDefult( @RequestBody @Valid Student student) {
        return "success";
    }

说明:

  • 如果在 Controller 中 指定了 @Validated(OnCreate.class) 分组,则 Student 中只会使用含有 groups = OnCreate.class 的注解进行字段校验,其他分组和默认注解不会进行校验;

  • 如果在 Controller 中 指定了 @Validated(OnUpdate.class) 分组,则 Student 中只会使用含有 groups = OnUpdate.class 的注解进行字段校验,其他分组和默认注解不会进行校验;

  • 如果在 Controller 中 没有指定任何分组,只是使用了 @Valid 默认校验,则 Student 中会使用 不含 groups 的注解进行字段校验,只会校验默认注解;

所以:

  • 对于 @PostMapping(value = "/users") 接口:

    如果传参什么都不传,只传一个{}

    那么响应是 success

    因为该接口只会校验@Length(groups = OnCreate.class,min = 6, max = 20, message = "stuendId长度必须在6-20位之间"),而 @Length 通常不校验 null 值(它只校验只要不为null,长度就要符合)

  • 对于 @PutMapping(value = "/users") 接口:

    如果传参什么都不传,只传一个{}

    那么响应是 :

java 复制代码
{
    "code": 400,
    "message": "参数校验失败",
    "data": [
        {
            "field": "stuendId",
            "message": "stuendId不能为空"
        }
    ]
}

因为该接口只会校验@NotBlank(groups = OnUpdate.class,message = "stuendId不能为空")

  • 对于 @PostMapping(value = "/otherDefult") 接口:
    如果传参什么都不传,只传一个{}
    那么响应是 :
java 复制代码
{
    "code": 400,
    "message": "参数校验失败",
    "data": [
        {
            "field": "addresses",
            "message": "地址列表不能为空"
        },
        {
            "field": "stuendId",
            "message": "stuendId不能为null"
        },
        {
            "field": "teacherName",
            "message": "teacherName不能为null"
        },
        {
            "field": "roles",
            "message": "roles不能为空"
        },
        {
            "field": "name",
            "message": "用户名不能为空"
        }
    ]
}

该接口里所有注解里不含 groups 的默认注解都会进行校验,

如果你希望在调用 @PostMapping(value = "/users") 接口时(即 create 方法),既校验 OnCreate 的规则,又校验那些通用的(Default)默认规则(比如 teacherName 的非空),有两种通常的做法:

方法一:接口继承(推荐)

在 OnCreate 接口继承 Default 接口。

java 复制代码
import javax.validation.groups.Default;

public interface OnCreate extends Default {
}

这样配置后,调用 @Validated(OnCreate.class) 时,Spring 会同时校验 OnCreate 组 和 Default 组(即所有未加 group 的注解)。

方法二:在注解中手动指定多个组

在 Controller 中显式加上 Default.class。

java 复制代码
public String create(@RequestBody @Validated({OnCreate.class, Default.class}) Student student)

这样也会让两边的规则都生效。

相关推荐
LucDelton2 小时前
Java 读取无限量文件读取的思路
java·运维·网络
夹锌饼干2 小时前
mysql死锁排查流程--(处理mysql阻塞问题)
java·mysql
小信丶2 小时前
@EnableTransactionManagement注解介绍、应用场景和示例代码
java·spring boot·后端
To Be Clean Coder2 小时前
【Spring源码】createBean如何寻找构造器(四)——类型转换与匹配权重
java·后端·spring
-孤存-2 小时前
SpringBoot核心注解与配置详解
java·spring boot·后端
Hx_Ma163 小时前
BCrypt
java
We....3 小时前
鸿蒙与Java跨平台Socket通信实战
java·服务器·tcp/ip·arkts·鸿蒙
笃行客从不躺平3 小时前
Token 复习
java·分布式·spring cloud
Albert Edison3 小时前
【Python】函数
java·linux·python·pip