乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

校验 Validation — JSR-303 / JSR-380 Bean Validation

一句话定位:Bean Validation 是 Java 生态的"数据安检门"——在数据进入业务逻辑之前,用声明式注解检查字段是否合法,不合法直接拦下并返回清晰的错误信息。


为什么需要 Bean Validation?

乐途公司的孔蓝设计了一个商品上架接口,前端说"肯定传对了",结果后端收到:

  • 商品名称为空
  • 价格为负数
  • 库存数量是字符串 "abc"
  • 邮箱格式是 "not-an-email"

如果小崔在每个方法里手写 if-else 校验:

public void createProduct(ProductForm form) {
    if (form.getName() == null || form.getName().isBlank()) {
        throw new IllegalArgumentException("商品名称不能为空");
    }
    if (form.getPrice() == null || form.getPrice() <= 0) {
        throw new IllegalArgumentException("价格必须大于0");
    }
    // ... 20 个字段要校验,代码爆炸
}

Bean Validation 的解决思路:在字段上标注注解,让框架自动校验。


JSR-303 与 JSR-380

规范版本说明
JSR-303Bean Validation 1.0基础注解:@NotNull、@Size、@Min、@Max 等
JSR-380Bean Validation 2.0新增:@NotEmpty、@NotBlank、@Email、@Positive 等
Jakarta Validation 3.0继任者包名从 javax.validation 迁移到 jakarta.validation(Spring 6.0+)

Spring 从 3.0 开始集成 Bean Validation,默认使用 Hibernate Validator 作为实现。


核心注解速查

注解适用类型说明
@NotNull任意值不能为 null
@NotEmptyString / Collection / Map / 数组不能为 null 且长度 > 0
@NotBlankString不能为 null 且去除空白后长度 > 0
@Size(min, max)String / Collection / 数组长度/大小范围
@Min(value)数字最小值
@Max(value)数字最大值
@Positive数字必须为正数(> 0)
@PositiveOrZero数字必须 ≥ 0
@DecimalMinBigDecimal / String最小值(支持小数)
@DecimalMaxBigDecimal / String最大值
@Digits(integer, fraction)数字整数位和小数位限制
@EmailString邮箱格式
@Pattern(regexp)String正则匹配
@Past / @PastOrPresent日期过去的时间
@Future / @FutureOrPresent日期将来的时间
@AssertTrue / @AssertFalseboolean必须为 true / false
@Valid对象级联校验(校验嵌套对象)

乐途场景:商品与订单校验

商品上架表单

public class ProductForm {

    @NotBlank(message = "商品名称不能为空")
    @Size(max = 100, message = "商品名称最多 100 个字符")
    private String name;

    @NotNull(message = "价格不能为空")
    @Positive(message = "价格必须大于 0")
    @Digits(integer = 8, fraction = 2, message = "价格格式错误,最多 8 位整数 2 位小数")
    private BigDecimal price;

    @NotNull(message = "库存不能为空")
    @Min(value = 0, message = "库存不能为负数")
    @Max(value = 999999, message = "库存超出上限")
    private Integer stock;

    @NotBlank(message = "商品分类不能为空")
    private String category;

    @Email(message = "负责人邮箱格式不正确")
    private String managerEmail;

    @Pattern(regexp = "^FX-[0-9]+$", message = "商品编码格式必须为 FX-数字")
    private String productCode;

    // getters / setters...
}

Controller 中使用

@RestController
@RequestMapping("/api/products")
public class ProductController {

    @PostMapping
    public ResponseEntity<Product> createProduct(
            @RequestBody @Valid ProductForm form) {
        // 如果校验失败,Spring 自动抛 MethodArgumentNotValidException
        // 由 @ControllerAdvice 统一处理返回 400
        Product product = productService.create(form);
        return ResponseEntity.status(HttpStatus.CREATED).body(product);
    }
}

全局异常处理

@RestControllerAdvice
public class ValidationExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, Object>> handleValidationErrors(
            MethodArgumentNotValidException ex) {

        List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(error -> error.getField() + ": " + error.getDefaultMessage())
            .collect(Collectors.toList());

        Map<String, Object> body = Map.of(
            "error", "VALIDATION_FAILED",
            "message", "请求参数校验失败",
            "details", errors
        );

        return ResponseEntity.badRequest().body(body);
    }
}

分组校验

乐途公司的商品在不同阶段校验规则不同:

public interface CreateGroup {}   // 创建时校验
public interface UpdateGroup {}   // 更新时校验

public class ProductForm {

    @NotBlank(groups = CreateGroup.class)
    private String name;

    @NotNull(groups = {CreateGroup.class, UpdateGroup.class})
    @Positive
    private BigDecimal price;

    // 更新时允许不传库存(不修改)
    @NotNull(groups = CreateGroup.class)
    @Min(0)
    private Integer stock;
}
@PostMapping
public Product create(@RequestBody @Validated(CreateGroup.class) ProductForm form) {
    // 创建时校验 name、price、stock
}

@PutMapping("/{id}")
public Product update(@RequestBody @Validated(UpdateGroup.class) ProductForm form) {
    // 更新时只强制校验 price
}

自定义校验注解

乐途公司需要校验商品名称不能包含敏感词:

@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = NoSensitiveWordValidator.class)
public @interface NoSensitiveWord {
    String message() default "包含敏感词汇";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class NoSensitiveWordValidator implements ConstraintValidator<NoSensitiveWord, String> {

    private static final List<String> SENSITIVE_WORDS = List.of("暴力", "色情", "赌博");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null || value.isBlank()) {
            return true;  // @NotBlank 负责判空
        }
        return SENSITIVE_WORDS.stream().noneMatch(value::contains);
    }
}

使用:

public class ProductForm {
    @NotBlank
    @NoSensitiveWord(message = "商品名称包含违规内容")
    private String name;
}

注意事项

注意点说明
@Valid vs @Validated@Valid 是 JSR 标准,支持级联校验;@Validated 是 Spring 的,支持分组校验。两者可组合使用
校验顺序先类型转换,再字段校验。如果类型转换失败(如 "abc" → int),会抛 TypeMismatchException 而非校验错误
嵌套校验对象内部嵌套对象需加 @Valid,否则不会级联校验
集合校验List<@Valid ProductForm> 可对列表中每个元素校验(需 Bean Validation 2.0+)
Service 层校验在 Service 方法参数上加 @Validated,配合 @Valid 实现业务层校验
message 国际化在 ValidationMessages.properties 中配置 {javax.validation.constraints.NotBlank.message}=不能为空

常见面试题

Q1:@NotNull、@NotEmpty、@NotBlank 有什么区别?

@NotNull:不能为 null,但可以是空字符串 "" 或空集合。@NotEmpty:不能为 null 且长度/大小 > 0, "" 不通过。@NotBlank:只能用于 String,不能为 null 且 trim 后长度 > 0," " 也不通过。

Q2:为什么 @Valid 在嵌套对象上必须显式标注?

这是规范设计。@Valid 表示"级联校验",没有它,Validator 不会深入嵌套对象内部校验字段。这是为了防止无意识的深度遍历导致性能问题。

Q3:Spring 校验和 Hibernate Validator 的关系?

Spring 提供集成层(LocalValidatorFactoryBean、@Validated),Hibernate Validator 是规范的实现(Reference Implementation)。Spring 默认自动检测并装配 Hibernate Validator。

Q4:如何在校验失败时返回自定义错误码?

实现 ConstraintValidator 时,通过 context.buildConstraintViolationWithTemplate("错误信息").addConstraintViolation() 自定义消息。或在全局异常处理器中根据字段名映射错误码。

上一页
SpEL — Spring 表达式语言
下一页
类型转换与数据绑定 — Converter / DataBinder