美文网首页
spring validation的使用与自定义校验注解

spring validation的使用与自定义校验注解

作者: 缓慢移动的蜗牛 | 来源:发表于2019-07-26 14:05 被阅读0次

    1. validate介绍

    1.1 前言

    前端录入数据,在后台进行校验工作是必不可少的。例如:为空校验,邮箱格式校验,手机号校验等。
    后端校验的目的是避免用户绕过浏览器,使用http工具直接向后端请求一些违法数据,服务端的数据校验也是必要的,可以防止脏数据落到数据库中,如果数据库中出现一个非法的邮箱格式,也会让运维人员头疼不已。

    下面介绍spring 框架中的自动校验机制。

    1.2 框架提供的校验注解

    注解 说明
    @NotNull 值不能为空
    @Null 值必须为空
    @Pattern(regexp=) 字符串必须匹配正则表达式
    @Size(min=,max=) 集合的元素数量必须在min和max之间
    @CreditCardNumber(ignoreNonDigitCharacters) 字符串必须是信用卡号(按美国标准校验)
    @Email 字符串必须是Email邮箱
    @Length(min=,max=) 检查字符串的长度
    @NotBlank 字符串必须有字符
    @NotEmpty 字符串不为null,集合有元素
    @Range(min=,max=) 数字必须大于等于min,小于等于max
    @SafeHtml 字符串是安全的html
    @URL 字符串是合法的URL
    @AssertFalse 值必须是false
    @AssertTrue 值必须是true
    @DecimalMax(value=,inclusive=) 值必须小于等于(inclusive=true)/小于(inclusive=false) value属性指定的值,可以注解在字符串类型的属性上
    @DecimalMin(value=,inclusive=) 值必须大于等于(inclusive=true)/大于(inclusive=false) value属性指定的值,可以注解在字符串类型的属性上
    @Digits(integer=,fraction=) 数字格式检查,integer指定整数部分的最大长度,fraction指定小数部分的最大长度
    @Future 值必须是未来的日期
    @Past 值必须是过去的日期
    @Max(value=) 值必须小于等于value指定的值,不能注解在字符串类型的属性上
    @Min(value=) 值必须大于等于value指定的值,不能注解在字符串类型的属性上

    1.3 引入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.1.6.RELEASE</version>
    </dependency>
    <!--因为lombok ,让代码更简洁-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.8</version>
    </dependency>
    

    1.4 实体类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public class User implements Serializable {
        private static final long serialVersionUID = -8441401775719174836L;
    
        private Integer id;
        private String userName;
    
        @NotBlank(message = "密码不能为空")
        private String password;
    
        @Past(message = "生日必须是过去的时间")
        private Date birthDay;
    }
    

    1.5 控制层

    import javax.validation.Valid;
    import org.springframework.validation.BindingResult;
    /**
    * 说明:
    *      添加 BindingResult参数后,就算验证不通过,也能继续执行方法体
    *      BindingResult的参数的作用:
    *              方便记录日志(如果不想继续向下执行,可以直接再抛异常)
    * @param user
    * @param errors
    * @return
    */
    @PostMapping()
    @JsonView(User.UserSimpleView.class)
    public Object create(@Valid @RequestBody User user, BindingResult result){
        // 判断BindingResult是否保存错误的验证信息,如果有,则直接return
        if (result.hasErrors()) {
            Map<String, String> errors = Maps.newHashMap();
            result.getFieldErrors().stream().forEach(f -> errors.put(f.getField(), f.getDefaultMessage()));
            
            return errors;
        }
        
        //对 user的处理,然后返回
    
        return user;
    }
    

    2 Hibernate-validate工具类,手动调用校验返回结果

    2.1 添加 hibernate-validate依赖

    <!--
    如果引入了spring-boot-starter-web就可以不用引用下面的依赖了
    -->
    <dependency>
        <groupId>org.hibernate</groupId>
       <artifactId>hibernate-validator</artifactId>
        <version>6.0.18.Final</version>
    </dependency>
    

    2.2 接收处理结果,以及输出格式化的一个实体类

    import org.apache.commons.lang3.StringUtils;
    
    import java.text.MessageFormat;
    import java.util.Map;
    
    public class ValidationResult {
        /**
         * 是否有异常
         */
        private boolean hasErrors;
    
        /**
         * 异常消息记录
         */
        private Map<String, String> errorMsg;
    
        /**
         * 获取异常消息组装
         *
         * @return
         */
        public String getMessage() {
            if (errorMsg == null || errorMsg.isEmpty()) {
                return StringUtils.EMPTY;
            }
            StringBuilder message = new StringBuilder();
            errorMsg.forEach((key, value) -> {
                message.append(MessageFormat.format("{0}:{1} \r\n", key, value));
            });
            return message.toString();
        }
    
        public boolean hasErrors() {
            return hasErrors;
        }
    
        public boolean isHasErrors() {
            return hasErrors;
        }
    
        public void setHasErrors(boolean hasErrors) {
            this.hasErrors = hasErrors;
        }
    
        public Map<String, String> getErrorMsg() {
            return errorMsg;
        }
    
        public void setErrorMsg(Map<String, String> errorMsg) {
            this.errorMsg = errorMsg;
        }
    
        @Override
        public String toString() {
            return "ValidationResult{" +
                    "hasErrors=" + hasErrors +
                    ", errorMsg=" + errorMsg +
                    '}';
        }
    }
    

    2.3 创建工具类,提供公共方法校验,返回结果

    import org.apache.commons.collections.CollectionUtils;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.Validator;
    import javax.validation.groups.Default;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
    public class ValidateUtil {
    
        private ValidateUtil() {
        }
    
        /**
         * 验证器
         */
        private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
    
    
        /**
         * 校验实体,返回实体所有属性的校验结果
         *
         * @param obj
         * @param <T>
         * @return
         */
        public static <T> ValidationResult validateEntity(T obj) {
            //解析校验结果
            Set<ConstraintViolation<T>> validateSet = validator.validate(obj, Default.class);
            return buildValidationResult(validateSet);
        }
    
        /**
         * 校验指定实体的指定属性是否存在异常
         *
         * @param obj
         * @param propertyName
         * @param <T>
         * @return
         */
        public static <T> ValidationResult validateProperty(T obj, String propertyName) {
            Set<ConstraintViolation<T>> validateSet = validator.validateProperty(obj, propertyName, Default.class);
            return buildValidationResult(validateSet);
        }
    
        /**
         * 将异常结果封装返回
         *
         * @param validateSet
         * @param <T>
         * @return
         */
        private static <T> ValidationResult buildValidationResult(Set<ConstraintViolation<T>> validateSet) {
            ValidationResult validationResult = new ValidationResult();
            if (CollectionUtils.isNotEmpty(validateSet)) {
                validationResult.setHasErrors(true);
                Map<String, String> errorMsgMap = new HashMap<>();
                for (ConstraintViolation<T> constraintViolation : validateSet) {
                    errorMsgMap.put(constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage());
                }
                validationResult.setErrorMsg(errorMsgMap);
            }
            return validationResult;
        }
    }
    

    2.4 使用示例

    @PostMapping("/add2")
    public R<Boolean> add(@RequestBody MyTest myTest) throws Exception {
        ValidationResult validationResult = ValidateUtil.validateEntity(myTest);
        if (validationResult.hasErrors()) {
            return R.error(validationResult.getMessage());
        }
        return R.ok(true);
    }
    

    3 自定义校验注解

    自定义注解用途:校验字段的在数据库里的唯一性等

    3.1 定义注解类

    **
     * 自定义校验注解
     *  类似与 @NotEmpty
     *
     *  @Constraint(validatedBy = MyConstraintValidator.class)  指明这是一个校验的注解
     *      validatedBy = MyConstraintValidator.class 指定由MyConstraintValidator类去校验
     *
     *  必须定义三个成员  message,groups,payload
     *
     */
    @Target({ElementType.METHOD, ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = MyConstraintValidator.class)
    public @interface MyConstraint {
        String message() default "{javax.validation.constraints.NotEmpty.message}";
        Class<?>[] groups() default { };
        Class<? extends Payload>[] payload() default { };
    }
    

    3.2 定义校验类

    /**
     * 不用添加 @Component注解
     * spring会扫描实现了ConstraintValidator 接口的类后,然后自动管理
     *
     * ConstraintValidator<MyConstraint, String>:
     *   泛型参数说明:
     *          第一个参数 指定校验MyConstraint注解的字段
     *          第二参数 注解修改字段的类型
     * @author chennan
     * @date 2019.7.22 22:19
     */
    public class MyConstraintValidator implements ConstraintValidator<MyConstraint, String> {
    
        /**
         * 可以注入任何spring管理的服务
         */
        @Autowired
        private HelloService helloService;
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            System.out.println("------------MyConstraintValidator---------");
            System.out.println(value);
            System.out.println(helloService.greeting("tom"));
            System.out.println("------------MyConstraintValidator---------");
    
            //true-校验通过   false-校验不通过
            return false;
        }
    
        @Override
        public void initialize(MyConstraint constraintAnnotation) {
            System.out.println("my validator init");
        }
    }
    

    3.3 使用自定义校验注解

    public class User implements Serializable{
        private static final long serialVersionUID = -8441401775719174836L;
        @MyConstraint(message = "测试自定义注解")
        private String userName;
    }
    

    相关文章

      网友评论

          本文标题:spring validation的使用与自定义校验注解

          本文链接:https://www.haomeiwen.com/subject/glnrrctx.html