目录
[一、Spring Boot Validation简介](#一、Spring Boot Validation简介)
[1.1 什么是spring-boot-starter-validation?](#1.1 什么是spring-boot-starter-validation?)
[1.2 核心优势](#1.2 核心优势)
[2.1 添加依赖](#2.1 添加依赖)
[2.2 基础配置](#2.2 基础配置)
[3.1 常用校验注解](#3.1 常用校验注解)
[3.2 嵌套对象校验](#3.2 嵌套对象校验)
[4.1 DTO类定义校验规则](#4.1 DTO类定义校验规则)
[4.2 Controller层启用校验](#4.2 Controller层启用校验)
[4.3 统一异常处理](#4.3 统一异常处理)
[5.1 自定义校验规则](#5.1 自定义校验规则)
[5.2 分组校验](#5.2 分组校验)
[6.1 校验不生效的常见原因](#6.1 校验不生效的常见原因)
[6.2 国际化配置](#6.2 国际化配置)
[8.1 单元测试示例](#8.1 单元测试示例)
[8.2 API测试(使用MockMvc)](#8.2 API测试(使用MockMvc))
一、Spring Boot Validation简介
1.1 什么是spring-boot-starter-validation?
spring-boot-starter-validation
是Spring Boot对Bean Validation API(JSR 380)的封装实现,基于Hibernate Validator提供强大的数据校验功能。它能帮助开发者:
-
声明式校验:通过注解定义校验规则
-
统一错误处理:自动生成标准错误响应
-
多层级校验:支持DTO、Controller、Service各层
1.2 核心优势
-
零配置启动:自动装配Validator
-
丰富注解库:内置30+常用校验规则
-
高度可扩展:支持自定义校验规则
-
国际化支持:轻松实现多语言错误提示
二、快速集成与配置
2.1 添加依赖
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2.2 基础配置
application.yml:
java
spring:
messages:
basename: i18n/validation # 国际化文件路径
encoding: UTF-8
server:
error:
include-message: always # 显示具体错误信息
三、核心注解详解
3.1 常用校验注解
注解 | 适用类型 | 说明 | 示例 |
---|---|---|---|
@NotNull |
任意类型 | 值不能为null | @NotNull(message="ID必填") |
@NotEmpty |
String/Collection | 非空且长度/大小>0 | @NotEmpty |
@NotBlank |
String | 至少包含一个非空格字符 | @NotBlank |
@Size |
字符串/集合 | 长度/大小范围 | @Size(min=6, max=20) |
@Email |
String | 邮箱格式校验 | @Email |
@Pattern |
String | 正则表达式匹配 | @Pattern(regexp="^1[3-9]\\d{9}$") |
@Min /@Max |
数值类型 | 数值范围限制 | @Min(18) |
@Future /@Past |
时间类型 | 未来/过去时间校验 | @Future |
3.2 嵌套对象校验
java
public class OrderDTO {
@Valid // 启用嵌套校验
private UserDTO user;
@Valid
private List<@Valid ProductItem> items;
}
public class UserDTO {
@NotBlank
private String name;
@Email
private String email;
}
四、实战开发步骤
4.1 DTO类定义校验规则
java
public class UserCreateRequest {
@NotBlank(message = "{user.name.required}")
@Size(max = 50, message = "{user.name.length}")
private String name;
@Email(message = "{user.email.invalid}")
private String email;
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$",
message = "{user.password.policy}")
private String password;
}
4.2 Controller层启用校验
java
@PostMapping("/users")
public ResponseEntity<User> createUser(
@RequestBody @Valid UserCreateRequest request) {
// 业务逻辑处理
return ResponseEntity.ok(userService.create(request));
}
4.3 统一异常处理
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
return ResponseEntity.badRequest()
.body(new ErrorResponse("VALIDATION_FAILED", errors));
}
}
五、高级功能实现
5.1 自定义校验规则
步骤1:创建注解
java
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface PhoneNumber {
String message() default "{validation.phone.invalid}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
步骤2:实现校验逻辑
java
public class PhoneNumberValidator
implements ConstraintValidator<PhoneNumber, String> {
private static final Pattern PHONE_PATTERN =
Pattern.compile("^1[3-9]\\d{9}$");
@Override
public boolean isValid(String value,
ConstraintValidatorContext context) {
if (value == null) return true; // 允许空值,配合@NotNull使用
return PHONE_PATTERN.matcher(value).matches();
}
}
5.2 分组校验
java
public interface CreateGroup {}
public interface UpdateGroup {}
public class UserDTO {
@Null(groups = CreateGroup.class)
@NotNull(groups = UpdateGroup.class)
private Long id;
@NotBlank(groups = {CreateGroup.class, UpdateGroup.class})
private String name;
}
@PostMapping("/users")
public ResponseEntity<?> createUser(
@RequestBody @Validated(CreateGroup.class) UserDTO dto) {
// 创建逻辑
}
六、常见问题与解决方案
6.1 校验不生效的常见原因
缺少@Valid注解:Controller方法参数前忘记添加
错误异常处理:覆盖了默认的异常处理逻辑
静态嵌套类:DTO使用static内部类导致无法实例化
字段访问权限:校验字段需要getter方法
6.2 国际化配置
messages.properties:
java
user.name.required=用户名不能为空
user.email.invalid=邮箱格式不正确
validation.phone.invalid=手机号格式错误
validation_zh_CN.properties:
java
javax.validation.constraints.NotNull.message=不能为null
七、性能优化建议
-
避免过度校验:只在必要层级进行校验
-
合理使用校验组:减少不必要的校验逻辑
-
缓存Validator:重复使用Validator实例
java
@Bean
public Validator validator() {
return Validation.buildDefaultValidatorFactory().getValidator();
}
八、测试验证
8.1 单元测试示例
java
@SpringBootTest
public class UserValidationTest {
@Autowired
private Validator validator;
@Test
void whenInvalidEmail_thenValidationFails() {
UserCreateRequest request = new UserCreateRequest();
request.setEmail("invalid-email");
Set<ConstraintViolation<UserCreateRequest>> violations =
validator.validate(request);
assertThat(violations).hasSize(1);
}
}
8.2 API测试(使用MockMvc)
java
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mvc;
@Test
void createUser_withInvalidPassword_returnsBadRequest() throws Exception {
String json = """
{
"name": "test",
"email": "[email protected]",
"password": "123"
}
""";
mvc.perform(post("/users")
.contentType(APPLICATION_JSON)
.content(json))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errors[0]").value("password: 密码必须包含字母和数字"));
}
}
九、总结与最佳实践
-
分层校验原则:
-
Controller层:校验输入格式
-
Service层:校验业务规则
-
DAO层:校验数据完整性
-
-
错误消息规范:
-
使用明确的错误代码
-
保持消息内容用户友好
-
实现多语言支持
-
-
文档化校验规则:
-
在Swagger文档中展示参数约束
-
维护校验规则变更日志
-