spring-boot-starter-validation使用注解进行参数校验
前言
在 Spring Boot 开发中,数据校验(Bean Validation)是保证系统健壮性的第一道防线。它能让你告别满屏的 if (user == null),让代码更优雅、更具可读性。
我们通常使用的这些注解大多来自 Jakarta Bean Validation(原 JSR 303/349/380)规范及其参考实现 Hibernate Validator。
一、maven 依赖
对于 Spring Boot 2.x :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
对于 Spring Boot 3.x(Jakarta 包名):
依赖不变,但代码里引用的包名发生变化:javax.validation.* → jakarta.validation.*
如果没引入这个依赖:@Valid 不生效,校验注解写了等于没写
二、全局异常处理
全局异常处理用于参数校验失败以及业务异常时,将接口错误信息返回
1.定义通用返回类
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.全局异常处理类
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(最常用)
import jakarta.validation.Valid;
作用:触发校验,本身不定义规则
常见位置:Controller 方法参数、成员变量(级联校验)
示例:
@PostMapping("/add")
public void add(@RequestBody @Valid UserDTO dto) {
}
没有 @Valid,UserDTO 里的所有校验注解都不会执行
2.@Validated(Spring 提供,增强版)
import org.springframework.validation.annotation.Validated;
特点:支持分组校验,可用在 类、方法上
示例:
@Validated
@RestController
public class UserController {
}
90% 场景用 @Valid 即可
四、常见校验注解
1. @NotNull(最基础)
含义:
-
不能为 null
-
可以是空字符串 “”
适用类型:
- 所有对象类型(Integer、Long、String、List…)
@NotNull(message = "用户ID不能为空")
private Long userId;
2. @NotEmpty(比 NotNull 严格一点)
含义:
-
不能为 null
-
不能为 “”
-
集合不能是空集合
适用类型:
-
String
-
Collection / Map / Array
@NotEmpty
private String name;
@NotEmpty
private List<String> roles;
3. @NotBlank(字符串专用,最常用)
含义:
-
不能为 null
-
不能为 “”
-
不能是 " "(空白)
适用类型:
- 适用于 String
@NotBlank(message = "用户名不能为空")
private String username;
4. @Length(字符串长度,Hibernate 扩展)
适用类型:
- 只能用于 String
import org.hibernate.validator.constraints.Length;
@Length(min = 6, max = 20, message = "长度必须在6-20位之间")
private String password;
5. @Size(通用长度校验)
@Size(min = 1, max = 5)
private List<Long> ids;
适用类型:
-
String
-
Collection
-
Map
-
Array
与 @Length 区别:
注解 适用范围
@Length : String(Hibernate)
@Size : String + 集合(标准)
6. @Min / @Max(数值范围)
@Min(1)
@Max(100)
private Integer age;
⚠️ 不能校验字符串
7. @Positive / @PositiveOrZero(推荐)
@Positive // > 0
@PositiveOrZero // >= 0
@Positive
private Long amount;
8. @Email(邮箱校验)
@Email(message = "邮箱格式不正确")
private String email;
9. @Pattern(正则校验,强力)
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式错误")
private String phone;
10. @AssertTrue / @AssertFalse
@AssertTrue
private Boolean agree;
五、测试
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 方法
}
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 请求
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";
}
}
入参:
{
"age": 0,
"email": "string",
"amount": 0,
"addresses": [{}]
}
响应:
{
"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 参数里的校验才会生效
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";
}
}
入参:
http://localhost:8080/apiList/test?id=0&name=aaa
响应:
{
"code": 400,
"message": "参数校验失败",
"data": [
{
"field": "test.id",
"message": "id 必须大于 0"
},
{
"field": "test.name",
"message": "name长度必须在6-20位之间"
}
]
}
3.分组校验
对于同一个类,可能会用作不同接口的参数,这个时候可能就需要分组校验,让不同的接口指定不同的分组校验规则。
public interface OnCreate {
}
public interface OnUpdate {
}
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 方法
}
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")接口:
如果传参什么都不传,只传一个{},
那么响应是 :
{
"code": 400,
"message": "参数校验失败",
"data": [
{
"field": "stuendId",
"message": "stuendId不能为空"
}
]
}
因为该接口只会校验@NotBlank(groups = OnUpdate.class,message = "stuendId不能为空")
- 对于
@PostMapping(value = "/otherDefult")接口:
如果传参什么都不传,只传一个{},
那么响应是 :
{
"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 接口。
import javax.validation.groups.Default;
public interface OnCreate extends Default {
}
这样配置后,调用 @Validated(OnCreate.class) 时,Spring 会同时校验 OnCreate 组 和 Default 组(即所有未加 group 的注解)。
方法二:在注解中手动指定多个组
在 Controller 中显式加上 Default.class。
public String create(@RequestBody @Validated({OnCreate.class, Default.class}) Student student)
这样也会让两边的规则都生效。
六、自定义注解校验字段
下面进行自定义一个 @XssSafe 注解,用来对输入的字段进行校验,防止 xss 攻击。
定义注解:
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.PARAMETER}) // 可以放在字段或参数上
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Documented
@Constraint(validatedBy = XssValidator.class) // 关联校验逻辑类
public @interface XssSafe {
// 默认错误提示信息
String message() default "输入内容包含非法字符或潜在脚本风险";
// 分组校验(必要)
Class<?>[] groups() default {};
// 负载信息(必要)
Class<? extends Payload>[] payload() default {};
}
校验逻辑:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;
public class XssValidator implements ConstraintValidator<XssSafe, String> {
/**
* 预编译正则,并开启 CASE_INSENSITIVE (忽略大小写)
* 解释:
* <[^>]+> : 拦截所有 HTML 标签,如 <div>, <script>
* \b(on\w+)= : 拦截所有事件,如 onclick=, onerror=, onload= (\b保证是单词开头)
* javascript: : 拦截伪协议
*/
private static final Pattern XSS_PATTERN = Pattern.compile("<[^>]+>|\\b(on\\w+)=|javascript:", Pattern.CASE_INSENSITIVE);
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
// 使用 matcher 查找,只要发现任何匹配项就视为不合法
// find() 比 matches() 更灵活,只要包含成对的标签就拦截
return !XSS_PATTERN.matcher(value).find();
}
}
测试:
public class Teacher {
private String id;
@XssSafe
private String teacherName;
//省略get、set方法
}
如果是 Post 请求,必须使用 @Valid 来开启校验, @XssSafe 才会 生效
@RestController
@RequestMapping("/teacher")
public class TeacherController {
@PostMapping(value = "/getTeacherName")
public String getTeacherName(@RequestBody @Valid Teacher teacher ) {
return "success";
}
}
如果是 Get 请求,类上必须使用 @Validated 来开启校验, @XssSafe 才会生效
@Validated
@RestController
@RequestMapping("/teacher")
public class TeacherController {
@GetMapping("/name")
public String getTeacher(@RequestParam @XssSafe String teacherName) {
return "success";
}
}
效果:当 teacherName 字段传 “ssf<div>dsgfdg</div>” 、 “qqqq<script>” 等内容时,将提示 “输入内容包含非法字符或潜在脚本风险”
更多推荐




所有评论(0)