表单验证
Spring Boot 表单验证完整指南
Spring Boot 提供了强大的表单验证功能,主要基于 Java Bean Validation API (JSR 380) 实现。以下是完整的表单验证解决方案:
- 基础配置
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
启用验证
Spring Boot 自动配置了验证功能,无需额外配置。
- 常用验证注解
字段级验证
| 注解 | 说明 |
|——|——|
| @NotNull
| 不能为null |
| @NotEmpty
| 不能为null或空(字符串、集合、Map、数组) |
| @NotBlank
| 字符串不能为null且必须包含非空白字符 |
| @Size(min=, max=)
| 长度限制 |
| @Min(value)
| 数字最小值 |
| @Max(value)
| 数字最大值 |
| @DecimalMin(value)
| 小数值最小值 |
| @DecimalMax(value)
| 小数值最大值 |
| @Digits(integer=, fraction=)
| 数字位数限制 |
| @Email
| 电子邮件格式 |
| @Pattern(regexp=)
| 正则表达式匹配 |
| @Past
| 必须是过去日期 |
| @PastOrPresent
| 过去或现在日期 |
| @Future
| 必须是将来日期 |
| @FutureOrPresent
| 将来或现在日期 |
类级验证
| 注解 | 说明 |
|——|——|
| @Valid
| 级联验证关联对象 |
| @Validated
| 分组验证 |
- 完整实现示例
实体类定义
public class UserForm {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度必须在2-20个字符之间")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")
private String password;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Min(value = 18, message = "年龄必须大于18岁")
@Max(value = 100, message = "年龄必须小于100岁")
private Integer age;
@NotNull(message = "生日不能为空")
@Past(message = "生日必须是过去日期")
private LocalDate birthday;
// getters and setters
}
控制器处理
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public ResponseEntity<?> createUser(@Valid @RequestBody UserForm userForm,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// 处理验证错误
return ResponseEntity.badRequest().body(bindingResult.getAllErrors());
}
// 业务逻辑处理
return ResponseEntity.ok("用户创建成功");
}
}
- 高级功能
自定义验证注解
// 定义注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
String message() default "无效的手机号码";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// 实现验证逻辑
public class PhoneValidator implements ConstraintValidator<Phone, 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();
}
}
分组验证
public interface CreateGroup {}
public interface UpdateGroup {}
public class UserForm {
@Null(groups = CreateGroup.class, message = "ID必须为空")
@NotNull(groups = UpdateGroup.class, message = "ID不能为空")
private Long id;
// 其他字段...
}
// 控制器中使用
@PostMapping
public ResponseEntity<?> createUser(@Validated(CreateGroup.class) @RequestBody UserForm userForm) {
// ...
}
国际化错误消息
在
resources/messages.properties
中添加:userForm.username.notBlank=用户名不能为空 userForm.username.size=用户名长度必须在{min}到{max}个字符之间
在实体类中使用:
@NotBlank(message = "{userForm.username.notBlank}") @Size(min = 2, max = 20, message = "{userForm.username.size}") private String username;
统一异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidationException(
MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("status", HttpStatus.BAD_REQUEST.value());
body.put("errors", errors);
return ResponseEntity.badRequest().body(body);
}
}
- 前端表单验证配合
Thymeleaf 表单示例
<form th:action="@{/users}" th:object="${userForm}" method="post">
<div>
<label>用户名:</label>
<input type="text" th:field="*{username}">
<span th:if="${#fields.hasErrors('username')}"
th:errors="*{username}" class="error"></span>
</div>
<!-- 其他字段... -->
<button type="submit">提交</button>
</form>
- 测试验证
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void createUserWithInvalidData() throws Exception {
String invalidUserJson = "{\"username\":\"a\",\"password\":\"123\",\"email\":\"invalid\"}";
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(invalidUserJson))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errors").isArray())
.andDo(print());
}
}
总结
Spring Boot 表单验证提供了从简单到复杂的完整解决方案:
- 使用标准注解快速实现基础验证
- 通过自定义注解扩展验证规则
- 利用分组验证实现不同场景的验证逻辑
- 结合国际化支持多语言错误消息
- 统一异常处理返回友好的错误信息
- 前端后端协同验证提升用户体验
对于复杂业务场景,还可以结合 Spring 的 Validator
接口实现更灵活的验证逻辑。