全面掌握JSR303校验:从入门到实战

一、JSR303校验简介

JSR303是Java EE 6中的一项规范,全称为"Bean Validation 1.0",它定义了一套基于注解的JavaBean校验机制。通过简单的注解,我们可以优雅地完成参数校验工作,避免在业务代码中编写大量的校验逻辑。

为什么需要参数校验?

  1. 安全性:防止恶意用户绕过前端校验提交非法数据

  2. 健壮性:确保系统处理的数据符合预期格式

  3. 可维护性:将校验逻辑与业务逻辑分离,代码更清晰

  4. 一致性:统一校验规则,避免重复代码

二、环境搭建

1. 引入依赖

对于Spring Boot项目,只需添加以下starter依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

也可以直接用starter-web包,包含了JSR303

对于非Spring Boot项目,需要手动添加:

<!-- JSR303规范接口 -->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

<!-- Hibernate实现(最流行的实现) -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.2.0.Final</version>
</dependency>

<!-- 表达式语言依赖(用于动态消息) -->
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.el</artifactId>
    <version>3.0.0</version>
</dependency>

三、基础使用

1. 内置校验注解

JSR303提供了一系列内置注解:

注解适用类型说明
@Null任意必须为null
@NotNull任意必须不为null
@AssertTrueBoolean必须为true
@AssertFalseBoolean必须为false
@Min数字必须大于等于指定值
@Max数字必须小于等于指定值
@DecimalMin数字必须大于等于指定值(字符串形式)
@DecimalMax数字必须小于等于指定值(字符串形式)
@Size集合/字符串大小必须在指定范围内
@Digits数字必须是指定位数内的数字
@Past日期必须是过去的日期
@Future日期必须是将来的日期
@Pattern字符串必须匹配正则表达式
@Email字符串必须是电子邮件格式

2. 基本使用示例

public class UserDTO {
    
    @NotNull(message = "用户ID不能为空")
    private Long id;
    
    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 20, message = "用户名长度必须在2-20个字符之间")
    private String username;
    
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$", 
             message = "密码必须包含大小写字母和数字,且长度至少8位")
    private String password;
    
    @Email(message = "邮箱格式不正确")
    private String email;
    
    @Min(value = 18, message = "年龄必须大于等于18岁")
    @Max(value = 120, message = "年龄必须小于等于120岁")
    private Integer age;
    
    // getters and setters
}

3. 在Controller中使用校验

@RestController
@RequestMapping("/users")
public class UserController {
    
    @PostMapping
    public ResponseEntity<String> createUser(@RequestBody @Valid UserDTO userDTO) {
        // 业务逻辑
        return ResponseEntity.ok("用户创建成功");
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(
            @PathVariable @Min(1) Long id,
            @RequestParam @Pattern(regexp = "\\d{4}-\\d{2}-\\d{2}") String date) {
        // 业务逻辑
        return ResponseEntity.ok(new UserDTO());
    }
}

四、高级特性

1. 分组校验

通过分组可以实现不同场景下的差异化校验:

public interface CreateGroup {}
public interface UpdateGroup {}

public class UserDTO {
    @Null(groups = CreateGroup.class, message = "创建时ID必须为空")
    @NotNull(groups = UpdateGroup.class, message = "更新时ID不能为空")
    private Long id;
    
    // 其他字段...
}

// 使用分组校验
@PostMapping
public ResponseEntity<String> createUser(@RequestBody @Validated(CreateGroup.class) UserDTO userDTO) {
    // 业务逻辑
}

2. 级联校验

当对象包含其他对象时,可以使用@Valid进行级联校验:

public class OrderDTO {
    @Valid
    private UserDTO user;
    
    @Valid
    private List<@Valid OrderItemDTO> items;
    
    // 其他字段...
}

3. 自定义校验注解

当内置注解不能满足需求时,可以自定义校验注解:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface PhoneNumber {
    String message() default "手机号格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

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();
    }
}

// 使用自定义注解
public class UserDTO {
    @PhoneNumber
    private String phone;
}

五、异常处理

详情可看:Spring Boot全局异常处理最佳实践:@RestControllerAdvice深度解析-CSDN博客

校验失败会抛出MethodArgumentNotValidExceptionConstraintViolationException,我们可以统一处理:

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(
            MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach(error -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return ResponseEntity.badRequest().body(errors);
    }
    
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<Map<String, String>> handleConstraintViolationException(
            ConstraintViolationException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getConstraintViolations().forEach(violation -> {
            String path = violation.getPropertyPath().toString();
            String message = violation.getMessage();
            errors.put(path, message);
        });
        return ResponseEntity.badRequest().body(errors);
    }
}

六、最佳实践

  1. 合理使用message:提供清晰的错误提示,可以使用国际化消息

  2. 组合使用注解:如@NotNull@Size一起使用

  3. 避免过度校验:只在边界处进行严格校验

  4. 性能考虑:对于复杂校验,考虑异步处理

  5. 文档化:使用Swagger等工具展示校验规则

七、常见问题

1. 校验不生效怎么办?

  • 检查是否添加了@Valid@Validated注解

  • 确认依赖已正确引入

  • 检查字段访问权限(需要getter方法)

2. 如何校验集合?

public ResponseEntity<?> batchCreate(
        @RequestBody @Valid List<@Valid UserDTO> users) {
    // 业务逻辑
}

3. 如何动态修改message?

@Size(min = 6, max = 20, message = "{password.size}")
private String password;

// 在messages.properties中
password.size=密码长度必须在{min}到{max}个字符之间

结语

JSR303校验为Java开发者提供了一套优雅的参数校验解决方案。通过本文的学习,你应该已经掌握了从基础使用到高级特性的全部内容。在实际项目中,合理使用参数校验可以显著提高代码质量和系统安全性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值