Spring-Validation(数据校验)
Spring-Validation(数据校验)
Spring框架中的数据校验模块Validation主要用于验证用户输入或业务数据的合法性。它支持自定义验证逻辑,并与Spring MVC无缝集成。Spring的校验模块不仅支持原生的Validator接口,还兼容JSR-303/JSR-380(Bean Validation)标准,提供了强大的数据校验能力。
数据校验(Validation)模块中的核心组件
1.Validator接口
Validator是Spring提供的原生校验接口,用于实现自定义校验逻辑。它包含了两个核心方法。
boolean supports(Class<?> clazz);
判断当前校验器是否支持指定类型的对象
返回值:
true表示支持,false表示不支持
void validate(Object target, Errors errors);
执行校验逻辑,并将错误信息记录到Errors对象中
参数:
target:需要校验的对象
errors:用于存储校验错误信息
2.LocalValidatorFactoryBean工厂类
LocalValidatorFactoryBean是Spring框架中的一个类,用于在Spring应用中配置和提供Bean Vaildation的Validator实例。它实现了Spring的Validator接口,并将底层的Bean Validation提供者(如Hibernate Validator)集成到Spring应用中。
3.Errors接口
Errors接口用于存储校验过程中的错误信息,这里只介绍常用的方法。
void reject(String errorCode);
添加全局错误
参数:
errorCode:错误码
void rejectValue(@Nullable String field, String errorCode, String defaultMessage);
添加字段级别错误
参数:
field:字段名
errorCode:错误码
defaultMessage:错误回退的默认消息
boolean hasErrors();
判断是否存在错误
FieldError getFieldError(String field);
获取指定字段名关联的第一个错误
参数:
field:字段名
4.ValidationUtils工具类
ValidationUtils是Spring提供的一个工具类,封装了常见的校验操作,以下是常用方法。
public static void rejectIfEmpty(Errors errors, String field, String errorCode, String defaultMessage)
如果字段为空,则记录错误
参数:
errors:要在该Errors实例上添加错误信息
field:字段名
errorCode:错误码
defaultMessage:错误回退的默认消息
public static void rejectIfEmptyOrWhitespace(Errors errors, String field, String errorCode, String defaultMessage)
如果字段为空或仅包含空白字符,则记录错误
参数:
errors:要在该Errors实例上添加错误信息
field:字段名
errorCode:错误码
defaultMessage:错误回退的默认消息
5.BindingResult接口
BindingResult是Errors接口的子接口,用于在Spring MVC中存储校验结果
主要提供了获取字段错误信息、判断是否存在错误、添加自定义错误信息等功能。
6.JSR-303/JSR-380(Bean Validation)支持
Spring集成了JSR-303/JSR-380(Bean Validation)标准,支持通过注解方式定义校验规则。常用的注解如下
JSR-303/JSR-380(Bean Validation)常用注解
| 注解 | 说明 | 
|---|---|
| @NotNull | 限制必须不为null | 
| @Null | 限制必须为null | 
| @NotEmpty | 作用于集合、数组、字符串等不能为空并且长度不为0 | 
| @NotBlank | 作用于字符串不能为空并且不能只包含空白字符 | 
| @Size(min=xx,max=xx) | 限制集合、数组、字符串等长度必须在min-max的数值之间 | 
| @Max(xx) | 作用于数值类型,必须不大于指定的数值 | 
| @Min(xx) | 作用于数值类型,必须不小于指定的数值 | 
| @DecimalMax | 作用于数值类型(包括小数),必须不大于指定的数值 | 
| @DecimalMin | 作用于数值类型(包括小数),必须不小于指定的数值 | 
| @Past | 作用于日期或时间,必须在当前时间之前 | 
| @Future | 作用于日期或时间,必须在当前时间之后 | 
| @Pattern | 作用于字符串,必须符合指定的正则表达式 | 
| 作用于字符串,必须是有效的电子邮箱地址 | |
| @AssertTrue | 作用于布尔类型,必须为true | 
| @AssertFalse | 作用于布尔类型,必须为false | 
| @Valid | 用于标注对象,表示对象的属性也需要验证(级联验证) | 
Spring Validation(数据校验)特有的注解@Validated与JSR-303/JSR-380(Bean Validation)标准的注解@Valid的区别
相同点
- 目的:两者都用于在Spring框架中进行数据验证,确保对象的状态符合特定的规则和约束。
 - 触发验证:当标注在方法参数或对象属性上时,两者都会触发验证过程。
 - 级联验证:都支持级联验证,即可以验证嵌套对象。
 - 异常抛出:验证失败时,都会抛出响应的异常,如ConstraintViolationException或MethodArgumentNotValidException。
 
不同点
- 来源
 
@Valid:来源于JSR-303/JSR-380 Bean Validation规范,是Java标准的一部分。
@Validated:是Spring特有的注解,用于提供Spring的验证支持。
- 使用范围
 
@Valid:可以用于方法、构造函数、方法参数、成员属性上。
@Validated:通常用于类,用来启用该类的验证支持,也可以用于方法参数。
- 分组验证
 
@Valid:不支持分组验证。
@Validated:支持分组验证,可以指定一个或多个分组(使用groups属性)来执行部分验证。
- 与Spring的集成
 
@Valid:在Spring中,当用于方法参数时,会触发验证,但不会提供Spring特有的验证功能,如分组验证。
@Validated:与Spring的验证框架紧密集成,支持分组验证和Srping的验证器(Validator)。
- 异常处理
 
@Valid:验证失败时,会抛出ConstraintViolationException。
@Validated:验证失败时,可以抛出ConstraintViolationException或Spring特定的MethodArgumentNotValidException。
- 注解属性
 
@Valid:没有额外的属性。
@Validated:有value和groups属性,可以指定验证器或分组。
7.ConstraintValidator接口
ConstraintValidator是Java Bean Validation API(JSR-303/JSR-380)中的一个核心接口,用于实现自定义校验逻辑。通过实现这个接口,开发者可以创建自定义的校验注解,并将其应用到Java Bean的字段或方法上。
ConstraintValidator接口源码
public interface ConstraintValidator<A extends Annotation, T> {
	default void initialize(A constraintAnnotation) {
	}
	boolean isValid(T value, ConstraintValidatorContext context);
}
ConstraintValidator接口方法说明
default void initialize(A constraintAnnotation)
初始化校验器,在校验器实例化后,该方法会被调用一次
参数:
constrainAnnotation:自定义注解的实例,可以通过它获取注解中定义的参数
boolean isValid(T value, ConstraintValidatorContext context)
执行实际的校验逻辑
参数:
value:需要校验的值
context:校验上下文,用于自定义错误信息或控制校验行为
返回值:
true:校验通过
false:校验失败
数据校验(Validation)模块的四种实现方式
四种实现方式
- 通过实现Validator接口进行数据校验
 - 通过注解形式进行数据校验
 - 基于方法进行数据校验
 - 基于自定义校验注解进行数据校验
 
引入相关依赖
        <!--spring context依赖-->
        <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.11</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>7.0.5.Final</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>jakarta.el</artifactId>
            <version>4.0.1</version>
        </dependency>
通过实现Validator接口进行数据校验的示例
实体类
/**
 * 人信息类
 */
public class Person {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
Validator接口实现类
/**
 * 通过实现spring中的Validator类来实现数据校验
 */
public class PersonValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return Person.class.equals(clazz);
    }
    @Override
    public void validate(Object target, Errors errors) {
        //name不能为空
        ValidationUtils.rejectIfEmpty(errors, "name", "name.empty", "name is empty");
        //0<age<200
        Person person = (Person) target;
        if (0 >= person.getAge()) {
            errors.rejectValue("age", "age.error", "age <= 0");
        } else if (200 <= person.getAge()) {
            errors.rejectValue("age", "age.error", "age >= 200");
        }
    }
}
测试类
/**
 * 测试数据校验
 */
public class TestPerson {
    public static void main(String[] args) {
        //创建person对象
        Person person = new Person();
        person.setName("alex");
        person.setAge(20);
        //创建person对应databinder
        DataBinder dataBinder = new DataBinder(person);
        //设置校验器
        dataBinder.setValidator(new PersonValidator());
        //调用方法执行校验器
        dataBinder.validate();
        //输出校验结果
        BindingResult bindingResult = dataBinder.getBindingResult();
        System.out.println(bindingResult.getAllErrors());
    }
}
通过注解形式进行数据校验的示例
使用JSR-303/JSR-380(Bean Validation)标准的注解进行校验的实体类
/**
 * 用户信息类
 *
 * @NotNull 限制必须不为null
 * @NotEmpty 只作用于字符串类型,字符串不为空,并且长度不为0
 * @NotBlank 只作用于字符串类型,字符串不为空,并且trim()后不为空串
 * @DecimalMax(value) 限制必须为一个不大于指定值的数字
 * @DecimalMin(value) 限制必须为一个不小于指定值的数字
 * @Max(value) 限制必须为一个不大于指定值的数字
 * @Min(value) 限制必须为一个不小于指定值的数字
 * @Pattern(value) 限制必须符合指定的正则表达式
 * @Size(max,min) 限制字符长度必须在min到max之间
 * @Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
 */
public class User {
    @NotNull
    private String name;
    @Max(200)
    @Min(1)
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
java配置类(将LocalValidatorFactoryBean工厂类注册到容器中,用于对Validator进行依赖注入)
/**
 * 配置类
 */
@Configuration
@ComponentScan("com.shen.validator.modetwo")
public class ValidatorConfig {
    @Bean
    public LocalValidatorFactoryBean vaildator() {
        return new LocalValidatorFactoryBean();
    }
}
注入原生的校验器的校验类(jakarta.validation.Validator)
/**
 * 注入原生的校验器
 */
@Component
public class MyValidatorOne {
    @Autowired
    private Validator validator;
    /**
     * 返回是否有校验不通过的地方
     * @param user 用户实例
     * @return 是否有校验不通过的地方
     */
    public boolean validatorUser(User user) {
        Set<ConstraintViolation<User>> validate = validator.validate(user);
        for (ConstraintViolation<User> info : validate) {
            System.out.println(info.toString());
        }
        return validate.isEmpty();
    }
}
注入Spring提供的校验器的校验类(org.springframework.validation.Validator)
/**
 * 注入Spring依赖的校验器
 */
@Component
public class MyValidatorTwo {
    @Autowired
    private Validator validator;
    /**
     * 返回是否有校验不通过的地方
     *
     * @param user 用户实例
     * @return 是否有校验不通过的地方
     */
    public boolean validatorUser(User user) {
        BindException bindException = new BindException(user, user.getName());
        validator.validate(user, bindException);
        System.out.println(bindException.getBindingResult().getAllErrors());
        return !bindException.hasErrors();
    }
}
测试两种校验器的测试类
/**
 * 测试两种校验器
 */
public class TestMyValidator {
    @Test
    public void TestValidatorOne() {
        ApplicationContext context = new AnnotationConfigApplicationContext(ValidatorConfig.class);
        MyValidatorOne myValidatorOne = context.getBean(MyValidatorOne.class);
        User user = new User();
        user.setName("alex");
        user.setAge(20);
        boolean message = myValidatorOne.validatorUser(user);
        System.out.println(message);
    }
    @Test
    public void TestValidatorTwo() {
        ApplicationContext context = new AnnotationConfigApplicationContext(ValidatorConfig.class);
        MyValidatorTwo myValidatorTwo= context.getBean(MyValidatorTwo.class);
        User user = new User();
//        user.setName("alex");
//        user.setAge(20);
        boolean message = myValidatorTwo.validatorUser(user);
        System.out.println(message);
    }
}
基于方法进行数据校验的示例
使用JSR-303/JSR-380(Bean Validation)标准的注解进行校验的实体类
/**
 * 用户信息类
 *
 * @NotNull 限制必须不为null
 * @NotEmpty 只作用于字符串类型,字符串不为空,并且长度不为0
 * @NotBlank 只作用于字符串类型,字符串不为空,并且trim()后不为空串
 * @DecimalMax(value) 限制必须为一个不大于指定值的数字
 * @DecimalMin(value) 限制必须为一个不小于指定值的数字
 * @Max(value) 限制必须为一个不大于指定值的数字
 * @Min(value) 限制必须为一个不小于指定值的数字
 * @Pattern(value) 限制必须符合指定的正则表达式
 * @Size(max,min) 限制字符长度必须在min到max之间
 * @Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
 */
public class User {
    @NotNull
    private String name;
    @Max(200)
    @Min(1)
    private int age;
    @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$", message = "手机号码格式错误")
    @NotBlank(message = "手机号码不能为空")
    private String phone;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
}
java配置类(配置MethodValidationPostProcessor)
/**
 * 配置类
 */
@Configuration
@ComponentScan("com.shen.validator.modethree")
public class ValidatorConfig {
    @Bean
    public MethodValidationPostProcessor vaildator() {
        return new MethodValidationPostProcessor();
    }
}
基于方法实现的校验类
/**
 * 基于方法实现校验
 */
@Component
@Validated
public class MyValidator {
    public String testMethod(@NotNull @Valid User user) {
        return user.toString();
    }
}
测试校验方法的测试类
public class TestMyValidator {
    @Test
    public void testValidator() {
        ApplicationContext context = new AnnotationConfigApplicationContext(ValidatorConfig.class);
        MyValidator myValidator = context.getBean(MyValidator.class);
        User user = new User();
        user.setName("alex");
        user.setAge(550);
        user.setPhone("13999999999");
        System.out.println(myValidator.testMethod(user));
    }
}
基于自定义校验注解进行数据校验的示例
自定义注解
/**
 * 自定义注解,实现自定义数据校验
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
        validatedBy = {CanNotBlankValidator.class}
)
public @interface CanNotBlank {
    //默认提示的错误信息
    String message() default "该元素不能为空";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface List {
        CanNotBlank[] value();
    }
}
自定义校验器,关联自定义注解,重写校验方法
/**
 * 自定义校验器
 */
public class CanNotBlankValidator implements ConstraintValidator<CanNotBlank, String> {
    @Override
    public boolean isValid(String s, ConstraintValidatorContext context) {
	//字段不能包含空格字符
        if (null != s && s.contains(" ")) {
            //获取默认提示信息
            String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
            System.out.println("default message :" + defaultConstraintMessageTemplate);
            //禁用默认提示信息
            context.disableDefaultConstraintViolation();
            //设置提示语
            context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation();
            return false;
        }
        return true;
    }
}
引入自定义校验注解进行校验的实体类
public class User {
    @NotNull
    private
    String name;
    @Max(200)
    @Min(1)
    private int age;
    @CanNotBlank
    private String message;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}
java配置类(配置MethodValidationPostProcessor)
/**
 * 配置类
 */
@Configuration
@ComponentScan("com.shen.validator.modethree")
public class ValidatorConfig {
    @Bean
    public MethodValidationPostProcessor vaildator() {
        return new MethodValidationPostProcessor();
    }
}
基于方法实现的校验类
/**
 * 基于方法实现校验
 */
@Component
@Validated
public class MyValidator {
    public String testMethod(@NotNull @Valid User user) {
        return user.toString();
    }
}
测试自定义校验注解的测试类
public class TestUser {
    @Test
    public void testValidator() {
        ApplicationContext context = new AnnotationConfigApplicationContext(ValidatorConfig.class);
        MyValidator myValidator = context.getBean(MyValidator.class);
        User user = new User();
        user.setName("alex");
        user.setAge(20);
        user.setMessage("hello world");
        System.out.println(myValidator.testMethod(user));
    }
}
参考资料
https://docs.spring.io/spring-framework/reference/core/validation.html
                    
                
                
            
        
浙公网安备 33010602011771号