本文目录导读:

我来为你详细介绍Java表单校验的规范化流程。
校验分层架构
三层校验体系
Controller层 -> Service层 -> Persistence层
(参数校验) (业务校验) (数据库约束)
标准校验流程
基础校验框架搭建
Maven依赖配置
<!-- Spring Boot Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 或使用 Hibernate Validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
Controller层校验(参数校验)
DTO对象定义
@Data
public class UserCreateRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度需要在2-20个字符之间")
private String username;
@NotBlank(message = "密码不能为空")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,20}$",
message = "密码需包含大小写字母和数字,长度8-20位")
private String password;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotNull(message = "年龄不能为空")
@Min(value = 0, message = "年龄不能小于0")
@Max(value = 150, message = "年龄不能大于150")
private Integer age;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Future(message = "过期时间必须是未来时间")
private LocalDateTime expireTime;
}
Controller处理器
@RestController
@Validated
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public Result<Void> createUser(@Valid @RequestBody UserCreateRequest request) {
// 如果参数校验失败,会自动抛出异常
userService.createUser(request);
return Result.success();
}
@GetMapping("/{id}")
public Result<UserVO> getUser(@PathVariable @Min(1) Long id) {
return Result.success(userService.getUser(id));
}
}
分组校验
@Data
public class UserRequest {
@Null(groups = CreateGroup.class)
@NotNull(groups = UpdateGroup.class)
private Long id;
@NotBlank(groups = CreateGroup.class)
private String username;
// 更新时不需要修改密码
@NotBlank(groups = CreateGroup.class)
private String password;
private String email;
// 分组接口
public interface CreateGroup {}
public interface UpdateGroup {}
}
// Controller使用
@PostMapping
public Result<Void> create(@Validated(UserRequest.CreateGroup.class)
@RequestBody UserRequest request) {
userService.create(request);
return Result.success();
}
@PutMapping
public Result<Void> update(@Validated(UserRequest.UpdateGroup.class)
@RequestBody UserRequest request) {
userService.update(request);
return Result.success();
}
Service层校验(业务校验)
@Service
@Validated
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public void createUser(UserCreateRequest request) {
// 1. 参数校验(由Controller层保证)
// 2. 业务校验
validateBusinessRules(request);
// 3. 转换为实体
User user = new User();
BeanUtils.copyProperties(request, user);
// 4. 持久化
userRepository.save(user);
}
private void validateBusinessRules(UserCreateRequest request) {
// 用户名唯一性校验
if (userRepository.existsByUsername(request.getUsername())) {
throw new BusinessException(ErrorCode.USERNAME_EXISTS, "用户名已存在");
}
// 邮箱唯一性校验
if (userRepository.existsByEmail(request.getEmail())) {
throw new BusinessException(ErrorCode.EMAIL_EXISTS, "邮箱已被注册");
}
// 复杂度校验
if (isPasswordWeak(request.getPassword())) {
throw new BusinessException(ErrorCode.PASSWORD_WEAK, "密码强度不足");
}
}
private boolean isPasswordWeak(String password) {
// 实现密码强度检查逻辑
return false;
}
}
自定义校验注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface ValidPhone {
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class PhoneValidator implements ConstraintValidator<ValidPhone, String> {
private static final Pattern PHONE_PATTERN =
Pattern.compile("^1[3-9]\\d{9}$");
@Override
public boolean isValid(String phone, ConstraintValidatorContext context) {
if (phone == null) {
return true; // 由@NotBlank处理空值
}
return PHONE_PATTERN.matcher(phone).matches();
}
}
// 使用方式
@ValidPhone
private String phone;
全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidationException(MethodArgumentNotValidException ex) {
// 获取第一个错误信息
String message = ex.getBindingResult()
.getAllErrors()
.stream()
.findFirst()
.map(error -> error.getDefaultMessage())
.orElse("参数校验失败");
return Result.error(400, message);
}
@ExceptionHandler(ConstraintViolationException.class)
public Result<Void> handleConstraintViolation(ConstraintViolationException ex) {
String message = ex.getConstraintViolations()
.stream()
.findFirst()
.map(violation -> violation.getMessage())
.orElse("参数校验失败");
return Result.error(400, message);
}
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException ex) {
return Result.error(ex.getCode(), ex.getMessage());
}
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception ex) {
log.error("系统异常", ex);
return Result.error(500, "系统繁忙,请稍后重试");
}
}
校验规范建议
校验粒度控制
// ✅ 好的实践:明确的校验级别
public class OrderCreateRequest {
@NotNull
private Long productId; // 必须校验
private String couponCode; // 可选字段
public boolean hasCoupon() {
return couponCode != null && !couponCode.trim().isEmpty();
}
}
统一返回格式
@Data
public class Result<T> {
private int code;
private String message;
private T data;
private long timestamp;
public static <T> Result<T> success() {
return success(null);
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.code = 200;
result.message = "success";
result.data = data;
result.timestamp = System.currentTimeMillis();
return result;
}
public static <T> Result<T> error(int code, String message) {
Result<T> result = new Result<>();
result.code = code;
result.message = message;
result.timestamp = System.currentTimeMillis();
return result;
}
}
校验优先级示例
// 优先使用框架提供的注解
@NotEmpty(message = "订单号不能为空")
@Size(max = 32, message = "订单号长度不能超过32位")
private String orderNo;
// 复杂的业务逻辑使用自定义注解或方法校验
@ValidOrderStatus
private Integer status;
// 跨字段校验(如密码确认)
public class PasswordChangeRequest {
@NotBlank
private String newPassword;
@NotBlank
private String confirmPassword;
@AssertTrue(message = "两次输入的密码不一致")
public boolean isPasswordMatch() {
return newPassword != null && newPassword.equals(confirmPassword);
}
}
性能优化建议
// 使用正则表达式预编译
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
// 批量校验优化
public void batchValidate(List<UserCreateRequest> requests) {
List<String> errors = new ArrayList<>();
requests.parallelStream().forEach(request -> {
try {
validateSingle(request);
} catch (ValidationException e) {
synchronized (errors) {
errors.add("第" + index + "条数据: " + e.getMessage());
}
}
});
if (!errors.isEmpty()) {
throw new ValidationException(String.join("; ", errors));
}
}
测试用例示例
@SpringBootTest
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void testCreateUserValidation() throws Exception {
String requestBody = """
{
"username": "",
"email": "invalid-email",
"password": "123"
}
""";
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value("用户名不能为空"));
}
}
最佳实践总结
- 分层校验:Controller负责参数格式,Service负责业务逻辑
- 分组校验:根据不同场景使用不同校验规则
- 自定义注解:封装复杂校验逻辑,提高复用性
- 统一异常处理:减少重复代码,统一错误格式
- 清晰的错误信息:提供具体的错误描述,便于定位问题
- 性能考虑:避免在循环中重复创建对象,预编译正则表达式
- 安全性:避免暴露敏感信息(如SQL细节、系统路径等)
这样的校验流程能保证代码的可维护性、可读性和健壮性,同时提供良好的用户体验。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播