Java Springboot 参数校验

一、Bean Validation

        Java Bean Validation(JSR 380)是 Java EE 平台的一部分,它提供了一种在应用程序级别对 Java Bean 对象执行验证的机制。通过使用注解,开发人员可以在字段、方法和类级别定义验证规则,以确保数据的完整性和一致性。

在 Java Bean Validation 中,常用的注解包括:
在这里插入图片描述

@NotNull:用于对象的校验,确保 对象不为 null。
@NotBlank:验证对象是否不为空,相比 @NotNull 会去掉首尾空格,对象类型为 CharSequence。
@NotEmpty: 验证对象(如数组、Collection 集合、Map、String)是否 不为 NULL 并且长度或者大小不为空 。
@Size:用于验证对象(如数组、Collection 集合、Map、String)的长度或大小是否在给定的范围之内。
@Pattern:验证字符串是否匹配指定的正则表达式,null 值被认为是有效的格式。
@Email:验证是否符合电子邮件格式。
@Min:验证数字是否大于等于指定值,
@Max:验证数字是否小于等于指定值。
@AssertTrue:验证 Boolean 对象是否为 true。
@AssertFalse:验证 Boolean 对象是否为 false。
@NotBlank:验证 CharSequence 对象非 null,且长度必须大于 0。
@DecimalMin(value):被注解的对象必须是一个数字,其值必须大于等于指定的最小值,对象类型可以为 BigDecimal、BigInteger、CharSequence。
@DecimalMax:被注解的对象必须是一个数字,其值必须小于等于指定的最大值。
@Digits(integer,fraction):被注解的元素必须是一个数字,其值必须在指定的整数和小数部分的最大位数的范围之内。
@Past:被注解的元素必须是一个过去的日期。
@Future:被注解的元素必须是一个将来的日期。
@FutureOrPresent:被注解的元素必须是现在或将来的一个瞬间、日期或时间。
@PositiveOrZero:被注解的元素必须为正数或零。
@Positive:被注解的元素必须是正数(不包括 0)。
@NegativeOrZero:被注解的元素必须为负数或零。
@Negative:被注解的元素必须是负数(不包括 0)。
@Null:被注解的元素 必须是 NULL。

使用测试:

  1. 引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
  1. 创建实体用于校验
import lombok.Data;

import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;

@Data
public class UserInfo {
    @NotEmpty
    private String name;

    @Min(value = 18)
    private Integer age;

    @Email(message = "邮箱格式不正确")
    private String email;
}

  1. 创建访问接口
    接口中要使用 @Validated 或者 @Valid 使 Bean 验证生效
import com.yunfang.kuroshiro.domain.UserInfo;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/datavalid")
public class DataValidController {
    @GetMapping("/user")
    @ResponseBody
    public UserInfo getUserInfo(@Validated UserInfo userInfo) {
        return userInfo;
    }
}

  1. 测试结果
    在这里插入图片描述

    这里值显示了一个提示是因为框架异常处理的时候只取了一个
    

在这里插入图片描述

二、@Validated 分组校验

分组验证是为了在不同的验证场景下能够对对象的属性进行灵活地验证。例如:一般我们在对同一个对象进行保存或修改时,会使用同一个类作为入参。但是在保存获取修改的时候对同一个属性验证要求就可能会不同。这时候就需要分组验证。

对于定义分组有两点要特别注意:

定义分组必须使用接口。
要校验字段上必须加上分组,分组只对指定分组生效,不加分组不校验。

测试需求:在创建用户时,不需要校验 id,更新时则需要校验用户 id。

  1. 创建分组
    CreationGroup 用于创建时指定的分组:

public interface CreationGroup {
}

UpdateGroup 用于更新时指定的分组:

public interface UpdateGroup {
}

  1. 创建用户类
    属性 name 指定了组为 UpdateGroup 时验证 name 不为空,属性 id 指定了组为 UpdateGroup 时验证 id 不为空。
import com.yunfang.kuroshiro.validation.CreationGroup;
import com.yunfang.kuroshiro.validation.UpdateGroup;
import lombok.Data;

import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;

@Data
public class UserInfo {
    @NotEmpty(groups = {UpdateGroup.class})
    private String id;

    @NotEmpty(groups = {CreationGroup.class})
    private String name;

    @Min(value = 18)
    private Integer age;

    @Email(message = "邮箱格式不正确")
    private String email;
}

  1. 创建接口
    ValidationController 中新建两个接口 creat update
    creat 接口中 @Validated 中指定了只验证CreationGroup这个组
    update 接口中 @Validated 中指定了只验证 UpdateGroup 这个组
@Controller
@RequestMapping("/datavalid")
public class DataValidController {
    @GetMapping("/user")
    @ResponseBody
    public UserInfo getUserInfo(@Validated UserInfo userInfo) {
        return userInfo;
    }
    @GetMapping("/creat")
    @ResponseBody
    public UserInfo creat(@Validated({CreationGroup.class}) UserInfo userInfo) {
        return userInfo;
    }
    @GetMapping("/update")
    @ResponseBody
    public UserInfo update(@Validated({UpdateGroup.class}) UserInfo userInfo) {
        return userInfo;
    }
}

  1. 测试结果
    creat 接口:name 属性没有赋值
    在这里插入图片描述
    在这里插入图片描述
    update 接口:id 属性没有赋值
    在这里插入图片描述
    在这里插入图片描述

三、@Valid 嵌套校验

介绍嵌套校验之前先看一下两个概念:

  • 嵌套校验指的是在验证对象时,对对象内部包含的其他对象进行递归验证的过程。当一个对象中包含另一个对象作为属性,并且需要对这个被包含的对象也进行验证时,就需要进行嵌套校验。
  • 嵌套属性指的是在一个对象中包含另一个对象作为其属性的情况。换句话说,当一个对象的属性本身又是一个对象,那么这些被包含的对象就可以称为嵌套属性。

测试需求:在保存用户时,用户地址必须要填写

  1. 创建地址类 Address
import lombok.Data;
import javax.validation.constraints.NotBlank;

@Data
public class Address {
    @NotBlank
    private String country;

    @NotBlank
    private String city;
}

  1. Address 作为用户类的一个嵌套属性
  • 特别提示:想要嵌套校验生效,必须在嵌套属性上加 @Valid 注解。
import com.yunfang.kuroshiro.validation.CreationGroup;
import com.yunfang.kuroshiro.validation.UpdateGroup;
import lombok.Data;

import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

@Data
public class UserInfo {
    @NotEmpty(groups = {UpdateGroup.class})
    private String id;

    @NotEmpty(groups = {CreationGroup.class})
    private String name;

    @Min(value = 18)
    private Integer age;

    @Email(message = "邮箱格式不正确")
    private String email;

    @Valid
    @NotNull
    private Address address;
}

  1. 测试结果
    还是使用 user 接口测试
    在这里插入图片描述
    在这里插入图片描述
    可以看到使用了 @Valid 注解来对 Address 对象进行验证,这会触发对其中的 Address 对象的验证。通过这种方式,可以确保嵌套属性内部的对象也能够参与到整体对象的验证过程中,从而提高验证的完整性和准确性。

四、自定义参数校验约束注解和校验器

  1. @Constraint
    @Constraint 是 Java Bean Validation API 中的注解,用于自定义验证注解。通过使用 @Constraint 注解,您可以创建自己的验证注解,并定义验证逻辑。

在这里插入图片描述
@Constraint 有一个属性:

  • validatedBy:指定一个或多个实现了 ConstraintValidator 接口的验证器类,用于定义对应的验证逻辑。这个属性的值是一个 Class 数组,可以指定一个或多个验证器类。
  1. ConstraintValidator 接口
    ConstraintValidator 接口是 Java Bean Validation API 中的一部分,用于定义自定义验证逻辑的接口。通过实现 ConstraintValidator 接口,您可以创建自定义的验证器,用于验证自定义的验证注解。
    在这里插入图片描述

ConstraintValidator 接口有两个方法:

  • initialize 方法:在验证器初始化时调用,可以用来执行一些初始化操作。

  • isValid 方法:用于实现自定义的验证逻辑。在这个方法中,您可以编写验证逻辑,并返回一个布尔值表示验证是否通过。

通过实现 ConstraintValidator 接口,您可以根据自己的需求定义各种不同类型的验证逻辑。然后,您可以将这些自定义的验证逻辑应用到自定义的验证注解上,从而实现对特定类型的数据进行验证。

  1. 实战
    需求:实现一个性别验证的注解,用于验证属性值是否属于性别

定义一个接口,实现该接口之后将数据放到集合中,方便校验时获取。

/**
 * 判断值是否在枚举中需要实现此类
 * 实现该接口之后将数据放到集合中,方便校验时获取。
 */
public interface EnumValid {

    /**
     * 保存用于验证的数据源
     * @return
     */
    List<String> validValues();
}

这里定义一个枚举 GenderEnum,实现 EnumValid 接口,把用于验证的值放入到集合中。

/**
 * 性别枚举
 */
@AllArgsConstructor
@Getter
public enum GenderEnum implements EnumValid {
    MALE("MALE","男"),
    FEMALE("FEMALE","女"),
    UNKNOWN("UNKNOWN","位置");

    private final String code;
    private final String gender;

    @Override
    public List<String> validValues() {
        return Arrays.stream(GenderEnum.values()).map(GenderEnum::getCode).collect(Collectors.toList());
    }
}


定义一个参数校验器 InEnumValidator 实现 ConstraintValidator 接口,initialize 初始化时将实现了 EnumValid 接口,并重写了 validValues() 方法的子类中的集合赋值给list属性,然后在 isValid 方法中获取被 InEnum 注解标记的字段的值,并判断该字段的值是否在 list 中。

/**
 * 是否在枚举中验证器
 */
public class InEnumValidator implements ConstraintValidator<InEnum,String> {

    private List<String> list;

    /**
     * 初始化
     * @param constraintAnnotation
     */
    @Override
    public void initialize(InEnum constraintAnnotation) {
        //获取到注解value属性值
        Class<? extends EnumValid> value = (Class<? extends EnumValid>) constraintAnnotation.value();
        //获取该枚举类中的所有枚举常量
        EnumValid[] enumConstants = value.getEnumConstants();
        if(enumConstants != null){
            //获取到用于验证的数据源
            list = enumConstants[0].validValues();
        }

    }

    /**
     * 验证
     * @param value
     * @param context
     * @return
     */
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if(list!=null){
            return list.contains(value);
        }
        return false;
    }
}

定义一个约束注解 InEnum,它用于约束枚举值字段必须在集合中。

/**
 * 判断值是否在枚举中
 *
 */
@Documented
@Constraint(validatedBy = {InEnumValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(InEnum.List.class)
public @interface InEnum {
    /**
     * 提示信息
     * @return
     */
    String message() default "{javax.validation.constraints.NotEmpty.message}";

    /**
     * 分组
     * @return
     */
    Class<?>[] groups() default { };

    /**
     * 一定要有,不管是否用到。
     * payload 是一种用于将额外信息传递到验证约束的机制。
     * 实际上,payload 本身并不具有具体的功能,它只是一个用于携带额外信息的容器。
     * @return
     */
    Class<? extends Payload>[] payload() default { };

    /**
     * 枚举类(需要实现)
     * @return
     */
    Class<?> value();

    /**
     * Defines several {@code @NotEmpty} constraints on the same element.
     *
     * @see NotEmpty
     */
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    public @interface List {
        InEnum[] value();
    }
}

在用户实体用 InEnum 注解标记 gender 属性。

@Data
public class UserInfo {
    @NotEmpty(groups = {UpdateGroup.class})
    private String id;

    @NotEmpty(groups = {CreationGroup.class})
    private String name;

    @Min(value = 18)
    private Integer age;

    @Email(message = "邮箱格式不正确")
    private String email;

    @Valid
    @NotNull
    private Address address;

    @InEnum(GenderEnum.class)
    private String gender;
}

测试:
没有对属性 gender 赋值的时候, 属性 gender 验证不通过
因为提示语没有配置所以还是默认的提示
在这里插入图片描述
在这里插入图片描述
对属性 gender 赋值为 test 时, 属性 gender 验证不通过
在这里插入图片描述

对属性 gender 赋值为 MALE 时, 属性 gender 验证通过

在这里插入图片描述
在这里插入图片描述
从测试结果中可以看到校验是成功的

五、非 Controller 控制层参数校验(javax.validation.Validator)

在这里插入图片描述

Validator 接口是用于执行验证的主要接口之一。它允许您定义验证规则并对 Java 对象进行验证。

以下是 Validator 接口的一些主要方法:

  • <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups)

该方法用于验证给定对象是否符合指定的约束条件。
参数 object 是要验证的对象,参数 groups 可选,表示验证组。
返回一个 Set 集合,其中包含违反约束条件的 ConstraintViolation 对象。

  • <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups)

该方法用于验证给定对象的特定属性是否符合指定的约束条件。
参数 object 是要验证的对象,参数 propertyName 是属性名,参数 groups 可选,表示验证组。
返回一个 Set 集合,其中包含违反约束条件的 ConstraintViolation 对象。

  • <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups)

该方法用于验证给定属性值是否符合指定的约束条件,而不需要实际创建对象实例。
参数 beanType 是对象类型,参数 propertyName 是属性名,参数 value 是属性值,参数 groups 可选,表示验证组。
返回一个 Set 集合,其中包含违反约束条件的 ConstraintViolation 对象。

这些方法能够对 Java 对象进行全面的验证,从整个对象级别到单个属性值的验证,都可以通过 Validator 接口提供的方法来实现。

测试实体类还是使用 UserInfo

@Data
public class UserInfo {
    @NotEmpty(groups = {UpdateGroup.class})
    private String id;

    @NotEmpty(groups = {CreationGroup.class})
    private String name;

    @Min(value = 18)
    private Integer age;

    @Email(message = "邮箱格式不正确")
    private String email;

    @Valid
    @NotNull
    private Address address;

    @InEnum(GenderEnum.class)
    private String gender;
}

@Data
public class Address {
    @NotBlank
    private String country;

    @NotBlank
    private String city;
}

  1. 依赖 Spring 容器

使用 Validator 接口,可以创建验证器实例,并使用其方法来执行数据验证。在实际应用中,我们并不需要自己从头开始实现这一接口,因为已经有官方参考实现 ——Hibernate Validator。
因此,当我们使用 Spring 框架进行开发时,通常意味着我们可以直接利用 Spring 提供的集成,将 Validator 接口注入到我们的组件中,然后方便地使用它来进行数据校验。

请求接口

@Controller()
@RequestMapping("/validator_test")
public class ValidatorTestController {

    @Autowired
    private ValidatorTestSerivce validatorTestSerivce;

    @GetMapping("/submit")
    @ResponseBody
    public void sumit() {
        validatorTestSerivce.valid();
    }
}

service

@Service
public class ValidatorTestSerivce {
    @Autowired
    private Validator validator;

    public void valid(){
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(17);
        Address address = new Address();
        address.setCity("昆明");
        userInfo.setAddress(address);
        userInfo.setGender(GenderEnum.MALE.getCode());


        Set<ConstraintViolation<UserInfo>> constraintViolations = validator.validate(userInfo);
        System.out.println("validate 校验对象属性:");
        constraintViolations.stream().forEach(System.out::println);
        System.out.println("====================================");

        constraintViolations = validator.validate(userInfo, CreationGroup.class);
        System.out.println("validate 校验对象 验证组为 CreationGroup 组 的属性:");
        constraintViolations.stream().forEach(System.out::println);
        System.out.println("====================================");

        constraintViolations = validator.validateProperty(userInfo, "age");
        System.out.println("validateProperty 校验 age 属性是否合规:");
        constraintViolations.stream().forEach(System.out::println);
        System.out.println("====================================");

        constraintViolations = validator.validateProperty(userInfo, "address.country");
        System.out.println("validateProperty 校验 address 属性的 country 是否合规:");
        constraintViolations.stream().forEach(System.out::println);
        System.out.println("====================================");

        constraintViolations = validator.validateValue(UserInfo.class , "gender", "14" );
        System.out.println("validateValue 校验 UserInfo 对象 的 age 属性为 14 是否合规:");
        constraintViolations.stream().forEach(System.out::println);
        System.out.println("====================================");
    }
}

测试结果

validate 校验对象属性:
ConstraintViolationImpl{interpolatedMessage='不能为空', propertyPath=address.country, rootBeanClass=class com.yunfang.kuroshiro.domain.UserInfo, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
ConstraintViolationImpl{interpolatedMessage='最小不能小于18', propertyPath=age, rootBeanClass=class com.yunfang.kuroshiro.domain.UserInfo, messageTemplate='{javax.validation.constraints.Min.message}'}
====================================
validate 校验对象 验证组为 CreationGroup 组 的属性:
ConstraintViolationImpl{interpolatedMessage='不能为空', propertyPath=name, rootBeanClass=class com.yunfang.kuroshiro.domain.UserInfo, messageTemplate='{javax.validation.constraints.NotEmpty.message}'}
====================================
validateProperty 校验 age 属性是否合规:
ConstraintViolationImpl{interpolatedMessage='最小不能小于18', propertyPath=age, rootBeanClass=class com.yunfang.kuroshiro.domain.UserInfo, messageTemplate='{javax.validation.constraints.Min.message}'}
====================================
validateProperty 校验 address 属性的 country 是否合规:
ConstraintViolationImpl{interpolatedMessage='不能为空', propertyPath=address.country, rootBeanClass=class com.yunfang.kuroshiro.domain.UserInfo, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
====================================
validateValue 校验 UserInfo 对象 的 age 属性为 14 是否合规:
ConstraintViolationImpl{interpolatedMessage='{javax.validation.constraints.InEnum.message}', propertyPath=gender, rootBeanClass=class com.yunfang.kuroshiro.domain.UserInfo, messageTemplate='{javax.validation.constraints.InEnum.message}'}
====================================
  1. 不依赖 Spring 容器

JSR 303 是一种规范,意味着它 不依赖 Spring 容器 ,能用 Java 的地方都可以用它,那如果我们在非 Spring 框架的项目中怎么使用它呢?

其实,最主要的是能获取到 Validator 接口的实现,然后就可以进行验证了。
Validator 接口的实现可以通过如下代码获取,前提是要引入 Hibernate Validator 的依赖或者自己重新造轮子实现 Validator 接口。

测试:

 public static void main(String[] args){
        // 创建验证器工厂
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        UserInfo userInfo = new UserInfo();
        userInfo.setAge(17);
        Address address = new Address();
        address.setCity("昆明");
        userInfo.setAddress(address);
        userInfo.setGender(GenderEnum.MALE.getCode());


        Set<ConstraintViolation<UserInfo>> constraintViolations = validator.validate(userInfo);
        System.out.println("validate 校验对象属性:");
        constraintViolations.stream().forEach(System.out::println);
        System.out.println("====================================");

        constraintViolations = validator.validate(userInfo, CreationGroup.class);
        System.out.println("validate 校验对象 验证组为 CreationGroup 组 的属性:");
        constraintViolations.stream().forEach(System.out::println);
        System.out.println("====================================");

        constraintViolations = validator.validateProperty(userInfo, "age");
        System.out.println("validateProperty 校验 age 属性是否合规:");
        constraintViolations.stream().forEach(System.out::println);
        System.out.println("====================================");

        constraintViolations = validator.validateProperty(userInfo, "address.country");
        System.out.println("validateProperty 校验 address 属性的 country 是否合规:");
        constraintViolations.stream().forEach(System.out::println);
        System.out.println("====================================");

        constraintViolations = validator.validateValue(UserInfo.class , "age", 14 );
        System.out.println("validateValue 校验 UserInfo 对象 的 age 属性为 14 是否合规:");
        constraintViolations.stream().forEach(System.out::println);
        System.out.println("====================================");
    }

结果:

validate 校验对象属性:
ConstraintViolationImpl{interpolatedMessage='最小不能小于18', propertyPath=age, rootBeanClass=class com.yunfang.kuroshiro.domain.UserInfo, messageTemplate='{javax.validation.constraints.Min.message}'}
ConstraintViolationImpl{interpolatedMessage='不能为空', propertyPath=address.country, rootBeanClass=class com.yunfang.kuroshiro.domain.UserInfo, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
====================================
validate 校验对象 验证组为 CreationGroup 组 的属性:
ConstraintViolationImpl{interpolatedMessage='不能为空', propertyPath=name, rootBeanClass=class com.yunfang.kuroshiro.domain.UserInfo, messageTemplate='{javax.validation.constraints.NotEmpty.message}'}
====================================
validateProperty 校验 age 属性是否合规:
ConstraintViolationImpl{interpolatedMessage='最小不能小于18', propertyPath=age, rootBeanClass=class com.yunfang.kuroshiro.domain.UserInfo, messageTemplate='{javax.validation.constraints.Min.message}'}
====================================
validateProperty 校验 address 属性的 country 是否合规:
ConstraintViolationImpl{interpolatedMessage='不能为空', propertyPath=address.country, rootBeanClass=class com.yunfang.kuroshiro.domain.UserInfo, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
====================================
validateValue 校验 UserInfo 对象 的 age 属性为 14 是否合规:
ConstraintViolationImpl{interpolatedMessage='最小不能小于18', propertyPath=age, rootBeanClass=class com.yunfang.kuroshiro.domain.UserInfo, messageTemplate='{javax.validation.constraints.Min.message}'}
====================================

可以看到 依赖 Spring 容器 和 不依赖 Spring 容器 ,两者的调用结果都是相同的,不同之处就在于 Validator 的获取,前者直接从 Spring 容器 获取,后者需要通过工厂类获取,适用范围更广一点,在 Spring 框架中也能使用。

完结撒花。。。。。。。。。。。。。。。。。。。。。。。。。

  • 27
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值