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

前言  

        以前参与的新开放平台研发的过程当中,因为不一样的接口须要对不一样的入参进行校验,这就涉及到通用参数的校验封装,若是不进行封装,那么写出来的校验代码将会风格不统1、校验工具类不一致、维护风险高等其它因素,因而我对其公共的校验作了一个封装,达到了经过注解的方式便可实现参数统一校验。java

遇到的问题

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

  

传统的校验作法 

        以下校验若是一个实体里面上百个字段须要校验的话,对于维护起来是一个很麻烦的事情,并且不少校验能够经过jsr-303的注解方式统一处理,无需写一大堆if和else工具

 if(name == null) {
     //返回错误信息
 }else if(age == null) { //返回错误信息 }

 基于jsr-303定制后的校验

  1. 定义一个自定义非空注解
@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这个方法定义成抽象方法提供给不一样的校验器使用,避免其它校验器写重复的代码ui

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.测试类this

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个注解的方式,一个是校验为空,一个是校验是否位数字,分析完后那么就存在一个前后顺序问题(由于本身在本地测试出现有可能会先执行校验是否位数字的校验器,这时候就会出现空指针异常), 因此针对这个场景须要自定义一个顺序注解。spa

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

//顺序注解
@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;
	}
	public void setDate(String date) {
		this.date = date;
	}
	
	
}

  

First
Two

总结

    以上就是本篇博客涉及到技术点的全部代码,经过定制本身的校验器以知足公司业务场景,对于开发来讲统一了规范,统一风格,对之后维护仍是扩展都很是方便。 若是博文对你有帮助麻烦点个关注或者赞,谢谢!code

相关文章
相关标签/搜索