Spring Validation 校验框架原理深度解析

目录

  1. 引言
  2. 核心接口体系
  3. 约束注解解析与元数据
  4. 约束验证器执行机制
  5. 级联校验与对象图遍历
  6. 分组校验与组序列
  7. 消息国际化与模板机制
  8. @Validated 与 @Valid 深度对比
  9. Spring Boot 自动配置原理
  10. 控制器参数校验源码流程
  11. 自定义校验器高级用法
  12. 实战最佳实践
  13. 总结

1. 引言

Spring Validation 基于 JSR-380(Bean Validation 2.0)规范,构建了一套完整的声明式校验体系。但大多数开发者只停留在 @NotNull@NotBlank 的表层使用,对框架内部的执行机制知之甚少。

本文将深入剖析 Validation 框架从注解解析到约束执行的完整链路,揭示那些隐藏在 ConstraintValidator 背后的设计哲学。


2. 核心接口体系

2.1 接口继承关系图

creates

references

uses

receives

uses

uses

uses

uses

«interface»

Validator

+validate() : Set<ConstraintViolation>

+validateProperty() : Set<ConstraintViolation>

+validateValue() : Set<ConstraintViolation>

+getConstraintsForClass() : ConstrainedClass

+unwrap() : T

«interface»

ConstraintValidator<A extends Annotation, T>

+initialize(A) : void

+isValid(T, ConstraintValidatorContext) : boolean

«interface»

ConstraintViolation<T>

+getMessage() : String

+getMessageTemplate() : String

+getRootBean() : T

+getRootBeanClass() : Class<T>

+getInvalidValue() : Object

+getPropertyPath() : Path

+getConstraintDescriptor() : ConstraintDescriptor<A>

+getExecutableMetadata() : ExecutableMetadata

«interface»

ConstraintDescriptor<A extends Annotation>

+getAnnotation() : A

+getAttributes() : Map<String, Object>

+getConstraintValidatorClasses() : List<Class< extends ConstraintValidator>>

+getGroups() : Set<Class>

+getPayload() : Set<Class~ extends Payload>

+getMessageTemplate() : String

+getComposingConstraints() : Set<ConstraintDescriptor>

«interface»

ConstraintValidatorContext

+getDefaultConstraintMessageTemplate() : String

+disableDefaultConstraintViolation() : void

+buildConstraintViolationWithTemplate() : NodeContextBuilder

+getConstraintViolationCreationContexts() : List<ConstraintViolationCreationContext>

+unwrap() : T

«interface»

ConstraintValidatorFactory

+getInstance(Class<? extends ConstraintValidator>) : ConstraintValidator

+releaseInstance(ConstraintValidator, Class<? extends Annotation>) : void

«interface»

MessageInterpolator

+interpolate(String, Context) : String

+interpolate(String, Context, Locale) : String

«interface»

TraversableResolver

+isReachable(Object, Path.Node, Class~?, ElementType, Path) : boolean

+isCascadable(Object, Path.Node, Class~?, ElementType, Path) : boolean

«interface»

ParameterNameProvider

+getParameterNames(Method) : List<String>

+getParameterNames(Constructor~?) : List<String>

2.2 核心接口职责

ConstraintViolation 错误信息

消息模板

消息插值器

国际化消息

属性路径

根 bean

无效值

ConstraintValidator 校验逻辑

initialize()

解析注解元数据

isValid()

校验通过?

返回 true

自定义错误消息

返回 false

Validator 校验执行器

validate(obj)

获取 Bean 上的所有约束

遍历约束查找对应 Validator

调用 isValid 执行校验

收集 ConstraintViolation

返回校验结果集


3. 约束注解解析与元数据

3.1 约束注解结构

每个校验注解必须包含以下四个属性:

@Constraint(validatedBy)

«@interface»

校验注解

+String message

+Class[] groups

+Class[] payload

«interface»

ConstraintValidator

+void initialize(注解类型)

+boolean isValid(Object, ConstraintValidatorContext)

3.2 常用校验注解分类

布尔检查

@AssertTrue
必须为 true

@AssertFalse
必须为 false

数值检查

@Digits
整数和小数位数

@Negative / @Positive
负数/正数

@NegativeOrZero / @PositiveOrZero
含零边界

时间检查

@Past
过去时间

@Future
未来时间

@PastOrPresent / @FutureOrPresent
时间边界

格式检查

@Pattern
正则表达式

@Email
邮箱格式

@URL
URL 格式

范围检查

@Min / @Max
数值范围

@DecimalMin / @DecimalMax
高精度数值范围

@Size
长度范围

空值检查

@NotNull
不能为 null

@NotBlank
不能为空白字符

@NotEmpty
不能为空


4. 约束验证器执行机制

4.1 ConstraintValidator 生命周期

ConstraintValidatorContext ConstraintValidator ConstraintValidatorFactory ConstraintValidatorContext ConstraintValidator ConstraintValidatorFactory 初始化阶段(每个校验器实例执行一次) 解析注解属性 如:@Phone(regexp="...") 校验阶段(每次校验请求执行) alt [value == null 且 @NotNull 未设置] alt [自定义校验通过] alt [自定义校验失败] loop [遍历每个约束字段] 清理资源 getInstance(PhoneValidator.class) 创建实例 注入依赖(Spring 容器) initialize(@Constraint 注解) isValid(value, context) return true return true disableDefaultConstraintViolation() buildConstraintViolationWithTemplate() return false releaseInstance(validator)

4.2 内置约束验证器继承层次

ConstraintValidatorAdapter<A, T>

+void initialize(A)

+boolean isValid(T, ConstraintValidatorContext)

NotNullValidator

NotBlankValidator

NotEmptyValidator

EmailValidator

PatternValidator

SizeValidatorForCollection

SizeValidatorForCharSequence

SizeValidatorForMap

MinValidatorForNumber

MinValidatorForDate

DecimalMinValidator

DigitsValidatorForNumber

AssertTrueValidator

PastValidatorForDate

FutureValidatorForDate

4.3 组合约束与合成约束

合成约束 Constraint

@ValidEmail

EmailValidator

组合约束 ReportAsSingleViolation

任一失败

全部失败

@Email(组合约束)

@NotBlank + @Pattern

短路校验

报告单一违规

报告多个违规

简单约束 Constraint

@NotBlank

单一校验器

组合约束示例:

@NotBlank(message = "邮箱不能为空")
@Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", 
         message = "邮箱格式不正确")
@ReportAsSingleViolation  // 多个失败只报告一个
@Constraint(validatedBy = {})
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CompositeEmail {
    String message() default "邮箱格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

5. 级联校验与对象图遍历

5.1 级联校验执行流程

嵌套对象校验 Validator ConstraintValidationVisitor validate(user) 嵌套对象校验 Validator ConstraintValidationVisitor validate(user) alt [对象为 null] loop [深度优先遍历] alt [对象不为 null] 遍历 user 所有属性 发现 @Valid 标注的 address 字段 跳过(null 由 @NotNull 处理) validate(address, groups) 递归校验嵌套对象 处理 Address 的每个约束 检查 Address 是否有 @Valid 字段 Set~ConstraintViolation~ 合并违规集合 返回完整违规集合

5.2 对象图遍历策略

二级嵌套 Phone

一级嵌套 Address

根对象 User

id: @NotNull

name: @NotBlank

address: @Valid @NotNull

city: @NotBlank

district: @NotBlank

phones: @Valid @Size(min=1)

number: @Pattern

type: @NotNull

5.3 @Valid vs @Cascade

Cascade(Hibernate Validator 扩展)

进阶使用

更精细的控制

分组继承能力

可指定级联深度

Valid(javax.validation)

Spring 常用

级联触发校验

无法指定分组

由 TraversableResolver 控制


6. 分组校验与组序列

6.1 分组校验执行流程

校验执行

@Validated(Create.class)

只校验 Create 分组

@Validated({Create.class, Update.class})

校验多个分组

@Validated(Default.class)

隐式 Default 组

分组使用

@NotBlank(groups = Create.class)

@NotBlank(groups = Update.class)

@Email(groups = {Create.class, Update.class})

分组定义

interface Create {}

interface Update {}

interface Delete {}

interface Default {}

6.2 组序列与强制顺序

Group3 Group2 Group1 @Validated(UpdateSequence.class) Group3 Group2 Group1 @Validated(UpdateSequence.class) @GroupSequence({Basic.class, Extended.class, All.class}) 基本字段校验 id, username alt [Basic 组校验失败] 扩展字段校验 email, phone alt [Extended 组校验失败] 全部字段校验 自定义约束 校验 Basic 组 抛出异常 不执行后续组 校验 Extended 组 抛出异常 不执行后续组 校验 All 组

6.3 组序列定义

// 定义组序列
@GroupSequence({BasicInfo.class, ContactInfo.class, CompleteValidation.class})
public interface UserValidationSequence {
}

// 使用组序列
@Validated(UserValidationSequence.class)
public class User {}

// 分组接口定义
public interface BasicInfo {
    // 基本信息组
}

public interface ContactInfo {
    // 联系信息组
}

public class User {
    @NotNull(groups = BasicInfo.class)
    private Long id;
    
    @NotBlank(groups = {BasicInfo.class, ContactInfo.class})
    private String username;
    
    @Email(groups = ContactInfo.class)
    private String email;
    
    @Pattern(groups = CompleteValidation.class, regexp = "^1[3-9]\\d{9}$")
    private String phone;
}

7. 消息国际化与模板机制

7.1 消息插值流程

国际化处理

MessageInterpolator

消息模板 {约束名}.{验证器类型}

{validatedValue}

{formatter.format()}

{min}

{max}

加载 ValidationMessages.properties

查找模板键

解析占位符

参数替换

获取当前 Locale

查找 locale 匹配资源

回退到默认

7.2 消息模板语法

消息模板示例

@Size(min=2, max=10)
'长度必须在 {min} 到 {max} 之间'

@Pattern(regexp='[a-z]+')
'只允许小写字母'

@Min(value=0)
'值不能小于 {value}'

占位符类型

{0}

位置参数

{min}

注解属性

{max}

{validatedValue}

实际值

7.3 自定义消息资源

# ValidationMessages.properties
javax.validation.constraints.NotNull.message=不能为 null
javax.validation.constraints.NotBlank.message=不能为空
javax.validation.constraints.NotEmpty.message=集合不能为空
javax.validation.constraints.Size.message=长度必须在 {min} 到 {max} 之间
javax.validation.constraints.Email.message=邮箱格式不正确
javax.validation.constraints.Pattern.message=格式不匹配: {regexp}
javax.validation.constraints.Min.message=值不能小于 {value}
javax.validation.constraints.Max.message=值不能大于 {value}
javax.validation.constraints.DecimalMin.message=值不能小于 {value}
javax.validation.constraints.DecimalMax.message=值不能大于 {value}
javax.validation.constraints.AssertTrue.message=必须为 true
javax.validation.constraints.AssertFalse.message=必须为 false
javax.validation.constraints.Past.message=必须是过去的时间
javax.validation.constraints.Future.message=必须是未来的时间

# 自定义消息(包级别)
com.example.constraints.Phone.message=手机号格式不正确
com.example.constraints.IdCard.message=身份证号格式不正确

8. @Validated 与 @Valid 深度对比

8.1 功能对比图

Validated(Spring)

Valid(javax.validation)

共同点

触发参数校验

收集 ConstraintViolation

支持嵌套校验

嵌套对象级联校验

方法参数校验(需配合 @Validated)

构造器参数校验

不支持分组

不支持组序列

返回 BindingResult

方法级参数校验

支持分组校验

支持组序列

代理增强(AOP)

MethodValidationException

可指定验证范围

8.2 使用场景决策树

Controller 方法参数

@Valid 嵌套对象

方法参数(非 @RequestBody)

需要分组校验

需要组序列

@Bean 方法参数

需要校验

校验位置

@Valid + @RequestBody

@Valid + 嵌套属性

@Validated

@Validated + groups

@Validated + @GroupSequence

@Validated

配合 BindingResult

配合 @Validated

@Validated(groups=...)

@GroupSequence({...})


9. Spring Boot 自动配置原理

9.1 自动配置类加载链路

LocalValidatorFactoryBean

ValidationAutoConfiguration

Spring Boot 引导

SpringApplication.run()

AutoConfigurationImportSelector

MethodValidationPostProcessor

Validator 初始化

MessageInterpolator 配置

实现 Validator 接口

集成 Spring MessageSource

支持 SpEL 表达式

9.2 Validator 初始化流程

HibernateValidator LocalValidatorFactoryBean ValidationAutoConfiguration Spring Container HibernateValidator LocalValidatorFactoryBean ValidationAutoConfiguration Spring Container 支持增删改约束 仅标准约束 alt [存在 HibernateValidator] [仅依赖 Validation API] 创建 ValidatorFactory 检查类路径 是否有 HibernateValidator 创建 LocalValidatorFactoryBean 使用 HibernateValidator 实现 创建 LocalValidatorFactoryBean 使用可插拔验证器 执行 afterPropertiesSet() 初始化 MessageInterpolator 注册 TraversableResolver

9.3 依赖关系

LocalValidatorFactoryBean

-MessageInterpolator interpolator

-TraversableResolver resolver

+afterPropertiesSet()

+getValidator()

SpringConstraintValidatorFactory

-ConfigurableBeanFactory beanFactory

+getInstance()

+releaseInstance()

SpringMessageInterpolator

-MessageSource messageSource

+interpolate()


10. 控制器参数校验源码流程

10.1 请求处理完整链路

HandlerAdapter ExceptionHandler Validator RequestResponseBodyMethodProcessor HandlerAdapter DispatcherServlet HTTP Request HandlerAdapter ExceptionHandler Validator RequestResponseBodyMethodProcessor HandlerAdapter DispatcherServlet HTTP Request 处理 @RequestBody 参数 触发校验逻辑 loop [遍历约束] alt [存在违规] alt [存在 @Valid/@Validated] alt [抛出异常] [校验通过] POST /api/user doDispatch() getHandler(HandlerMapping) HandlerExecutionChain handle() resolveArgument() readWithMessageConverters() applyDefaultConversion() validate(value, groups) 获取约束描述符 获取对应 ConstraintValidator 调用 isValid() 检查每个约束 Set~ConstraintViolation~ 抛出 MethodArgumentNotValidException MethodArgumentNotValidException 异常 resolveException() 错误响应 400 Bad Request 校验后的对象 执行 Controller 方法 200 OK

10.2 校验触发的关键类

异常处理阶段

校验执行阶段

请求处理阶段

校验失败

DispatcherServlet

HandlerAdapter

RequestResponseBodyMethodProcessor

ModelAttributeMethodProcessor

WebRequestDataBinder

DataBinder

Validator.validate()

ConstraintViolationException

MethodArgumentNotValidException

@ExceptionHandler

10.3 全局异常处理

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleValidationException(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage())
        );
        return Result.error(400, "参数校验失败", errors);
    }
    
    @ExceptionHandler(ConstraintViolationException.class)
    public Result<?> handleConstraintViolation(ConstraintViolationException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getConstraintViolations().forEach(violation -> {
            String field = violation.getPropertyPath().toString();
            errors.put(field, violation.getMessage());
        });
        return Result.error(400, "参数校验失败", errors);
    }
    
    @ExceptionHandler(MethodValidationException.class)
    public Result<?> handleMethodValidation(MethodValidationException ex) {
        // @Validated 方法参数校验异常
        return Result.error(400, "方法参数校验失败", ex.getViolations());
    }
}

11. 自定义校验器高级用法

11.1 依赖注入的校验器

生命周期管理

initialize()

缓存依赖

isValid() 使用缓存

销毁时清理

Spring 集成方式

实现 ApplicationContextAware

实现 InitializingBean

注入 Service/Mapper

传统 ConstraintValidator

无状态

每次创建新实例

无法注入 Spring Bean

@Component
public class UserServiceAwareValidator implements ConstraintValidator<UniqueUsername, String>,
                                                  ApplicationContextAware {
    
    private ApplicationContext context;
    private UserMapper userMapper;
    
    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.context = context;
    }
    
    @Override
    public void initialize(UniqueUsername constraintAnnotation) {
        this.userMapper = context.getBean(UserMapper.class);
    }
    
    @Override
    public boolean isValid(String username, ConstraintValidatorContext context) {
        if (username == null || username.isBlank()) {
            return true; // @NotBlank 处理
        }
        return !userMapper.existsByUsername(username);
    }
}

11.2 多字段联合校验

校验逻辑

FieldMatch

first

second

matchType

获取 first 值

获取 second 值

根据 matchType 比较

返回校验结果

@Constraint(validatedBy = FieldMatchValidator.class)
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
public @interface FieldMatch {
    String message() default "字段不匹配";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    
    String first();
    String second();
    Class<? extends Comparator> matchType() default EqualMatch.class;
    
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        FieldMatch[] value();
    }
}

// 使用示例
@FieldMatch(first = "password", second = "confirmPassword", 
            message = "两次密码输入不一致")
public class PasswordResetRequest {
    private String password;
    private String confirmPassword;
}

11.3 动态分组校验

public class DynamicGroupValidator implements ConstraintValidator<DynamicGroup, Object> {
    
    @Override
    public void initialize(DynamicGroup constraintAnnotation) {}
    
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value == null) return true;
        
        // 动态决定分组
        Class<?>[] dynamicGroups = determineGroups(value);
        
        // 手动触发分组校验
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        Set<ConstraintViolation<Object>> violations = validator.validate(value, dynamicGroups);
        
        // 如果有违规,复制到当前上下文
        if (!violations.isEmpty()) {
            context.disableDefaultConstraintViolation();
            violations.forEach(v -> {
                context.buildConstraintViolationWithTemplate(v.getMessage())
                       .addConstraintViolation();
            });
            return false;
        }
        return true;
    }
    
    private Class<?>[] determineGroups(Object value) {
        if (value instanceof User user) {
            return user.isAdmin() ? 
                new Class[]{AdminGroup.class, UserGroup.class} : 
                new Class[]{UserGroup.class};
        }
        return new Class[]{Default.class};
    }
}

12. 实战最佳实践

12.1 分层校验策略

DAO 层

Service 层

Controller 层

@Validated 触发基本校验

@Validated(BasicInfo.class)

@Validated 触发业务校验

@Validated(BusinessInfo.class)

唯一性校验

数据库约束

12.2 性能优化策略

性能优化

短路校验

@GroupSequence 顺序

@ReportAsSingleViolation

对象复用

ObjectPool 或 ThreadLocal

避免重复创建

批量校验

validateValue() 批量预检

减少数据库查询

12.3 统一响应封装

@Data
public class ValidationResult {
    private boolean valid;
    private Map<String, String> errors;
    
    public static ValidationResult from(Set<ConstraintViolation<?>> violations) {
        ValidationResult result = new ValidationResult();
        result.setValid(violations.isEmpty());
        result.setErrors(violations.stream()
            .collect(Collectors.toMap(
                v -> v.getPropertyPath().toString(),
                ConstraintViolation::getMessage,
                (e1, e2) -> e1 + "; " + e2
            )));
        return result;
    }
}

// 全局异常处理
@RestControllerAdvice
public class ValidationExceptionHandler {
    
    @ExceptionHandler({MethodArgumentNotValidException.class, 
                       BindException.class})
    public Result<ValidationResult> handleValidationException(Exception ex) {
        BindingResult bindingResult = ex instanceof MethodArgumentNotValidException ?
            ((MethodArgumentNotValidException) ex).getBindingResult() :
            ((BindException) ex).getBindingResult();
        
        Set<ConstraintViolation<Object>> violations = 
            bindingResult.getAllErrors().stream()
                .map(error -> (ConstraintViolation<Object>) error)
                .collect(Collectors.toSet());
        
        return Result.error(400, "参数校验失败", 
            ValidationResult.from(violations));
    }
}

13. 总结

13.1 核心组件关系图

上下文层

插值层

执行层

解析层

注解层

@Constraint

@Valid / @Validated

@NotNull / @NotBlank / ...

ConstraintDescriptor

ConstraintAnnotationParser

ConstraintValidationInitializer

Validator

ConstraintValidator

ConstraintViolation

MessageInterpolator

MessageSource

ValidationMessages

ConstraintValidatorContext

ConstraintViolationContext

Path / PropertyPath

13.2 关键知识点

概念 说明
ConstraintValidator 校验逻辑执行器,包含初始化和校验两个阶段
ConstraintViolation 保存校验失败信息,包含消息、路径、属性值
ConstraintDescriptor 约束的元数据描述器
级联校验 通过 @Valid 触发嵌套对象的递归校验
分组校验 通过 groups 属性实现不同场景的差异化校验
组序列 通过 @GroupSequence 定义校验顺序,支持短路
消息插值 支持占位符替换和国际化消息解析

13.3 最佳实践总结

  1. 分层校验:Controller 做基本校验,Service 做业务校验,DAO 做唯一性校验
  2. 分组校验:使用 groups 区分创建、更新、删除等不同场景
  3. 组序列:使用 @GroupSequence 实现短路校验,避免无效校验
  4. 国际化:统一使用消息模板,通过 ValidationMessages.properties 管理
  5. 性能:避免在校验器中进行数据库查询,使用预校验机制
  6. 异常处理:统一封装 ValidationException,返回结构化的错误信息
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐