手把手写一个基于Spring Boot框架下的参数校验组件

Posted fjhdfjb

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手写一个基于Spring Boot框架下的参数校验组件相关的知识,希望对你有一定的参考价值。

手把手写一个基于Spring Boot框架下的参数校验组件(JSR-303)

 

前言  

        之前参与的新开放平台研发的过程中,由于不同的接口需要对不同的入参进行校验,这就涉及到通用参数的校验封装,如果不进行封装,那么写出来的校验代码将会风格不统一、校验工具类不一致、维护风险高等其它因素,于是我对其公共的校验做了一个封装,达到了通过注解的方式即可实现参数统一校验。

 

遇到的问题

                      在封装的时候就发现了一个问题,我们是开放平台,返回的报文都必须是统一风格,也就是类似于{code:999,msg:"参数校验失败",data:null} 这种,但是原生的JSR303并不支持自定义的字段,所以需要自定义校验注解。针对这个问题我参考一些JSR303的资料,对其进行了一个定制扩展,以达到开发人员不需要关注捕捉和封装返回信息。

 

  

 

传统的校验做法 

        如下校验如果一个实体里面上百个字段需要校验的话,对于维护起来是一个很麻烦的事情,而且很多校验可以通过jsr-303的注解方式统一处理,无需写一大堆if和else

 

 if(name == null) {

     //返回错误信息

 }else if(age == null) {

     //返回错误信息

}

 基于jsr-303定制后的校验

定义一个自定义非空注解

 

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })

@Retention(RUNTIME)

@Documented

@Constraint(validatedBy = { CorpNotEmptyValidator.class })

public @interface CorpNotEmpty {

 

    //自定义字段

    String field() default  "";

   //返回错误码

    String code() default  "0";

    //错误消息

    String message() default "{javax.validation.constraints.NotNull.message}";

 

    Class<?>[] groups() default { };

 

    Class<? extends Payload>[] payload() default { };

 

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })

    @Retention(RUNTIME)

    @Documented

    @interface List {

        CorpNotEmpty[] value();

    }

}

 

   2.定义非空注解对应的校验器, initialize和isValid作用描述如下:

 

  initialize方法主要是初始化ReturnCodeModel,用于当校验参数不通过后返回,ReturnCodeModel里面主要是封装了返回体,如 code,message等

       isValid主要是自定义校验器的校验规则,如下判断是否为空使用 StringUtils.isEmpty方法,如果校验不通过则set flag为false,然后调用基类的isValid方法,该基类方法会判断flag是否为false,如果是false说明不通过

 

public class CorpNotEmptyValidator extends BaseCorpValidator<CorpNotEmpty,String> {

 

 

    @Override

    public void initialize(CorpNotEmpty constraintAnnotation) {

        model = new ReturnCodeModel();

        model.setCode(constraintAnnotation.code());

        model.setErrorMsg(constraintAnnotation.message());

        model.setField(constraintAnnotation.field());

    }

 

    @Override

    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {

        System.out.println("1");

        if(StringUtils.isEmpty(s)){

            model.setFlag(false);

        }else{

            model.setFlag(true);

        }

        return super.isValid(s,constraintValidatorContext);

 

    }

}

 

  3.定义一个基类,实现 ConstraintValidator,主要是因为需要把isValid这个方法定义成抽象方法提供给不同的校验器使用,避免其它校验器写重复的代码

 

 

public abstract class BaseCorpValidator<A extends Annotation,B> implements ConstraintValidator<A ,B> {

    protected ReturnCodeModel model = null;

 

    @Override

    public boolean isValid(B value, ConstraintValidatorContext context) {

        if(!model.getFlag()){

            context.disableDefaultConstraintViolation();

            context.buildConstraintViolationWithTemplate(JSON.toJSONString(model)).addConstraintViolation();

            return false;

        }

        return true;

 

    }

 

}

 

   4.测试类

 

 

public class TestV1 {

    

    static ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();

    static Validator validator = validatorFactory.getValidator();

    

    public static void main(String[] args) {

        UserModel userModel = new UserModel();

        userModel.setName("aa");

        userModel.setDate("2011");

        Set<ConstraintViolation<UserModel>> constraintViolations  = validator.validate(userModel);

        //判断constraintViolations是否为空,不为空说明校验不通过,拿到ReturnCodeModel里面的错误信息后返回给客户端

        if(!constraintViolations.isEmpty()){

            for (ConstraintViolation<?> item : constraintViolations) {

                ReturnCodeModel codeModel = JSON.parseObject(item.getMessage(), ReturnCodeModel.class);

                System.out.println(JSON.toJSONString(codeModel));

            }

        }

    }

 

}

 

画外音:场景考虑

  1.比如name这个字段,要满足既不能为空又只能为数字这2个情况,如果把2个校验方法都写在同一个校验器,则其他开发使用的时候也会影响到,所以需要有2个注解的方式,一个是校验为空,一个是校验是否位数字,分析完后那么就存在一个先后顺序问题,所以针对这个场景需要自定义一个顺序注解。

 

  如下代码,在需要校验的model实体上加入@GroupSequence注解,这样校验器底层会帮我们按照顺序依次处理

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

//顺序注解

@GroupSequence({

    First.class,

    Two.class,

    Three.class,

    UserModel.class

})

public class UserModel {

    @CorpMustNumber(code="-2",message="必须数字",groups=Two.class)//在执行数字校验

    @CorpNotEmpty(code="-1",message="姓名必填",groups=First.class)//先执行非空

    private String name;

    @CorpNotEmpty(code="-1",message="日期必填")

    private String date;

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public String getDate() {

        return date;

    }

    pu

以上是关于手把手写一个基于Spring Boot框架下的参数校验组件的主要内容,如果未能解决你的问题,请参考以下文章

手把手教你手写一个最简单的 Spring Boot Starter

手把手教你利用Spring Boot实现各种参数校验

微服务中基于Spring Boot的maven分布式项目框架的搭建

Spring Boot2.0之纯手写框架

Mybatis-Spring Boot 手写SQL语句

带你手写基于 Spring 的可插拔式 RPC 框架通信协议模块