1. 程式人生 > >手把手寫一個基於Spring Boot框架下的引數校驗元件(JSR-303)

手把手寫一個基於Spring Boot框架下的引數校驗元件(JSR-303)

前言  

        之前參與的新開放平臺研發的過程中,由於不同的介面需要對不同的入參進行校驗,這就涉及到通用引數的校驗封裝,如果不進行封裝,那麼寫出來的校驗程式碼將會風格不統一、校驗工具類不一致、維護風險高等其它因素,於是我對其公共的校驗做了一個封裝,達到了通過註解的方式即可實現引數統一校驗。

遇到的問題

                      在封裝的時候就發現了一個問題,我們是開放平臺,返回的報文都必須是統一風格,也就是類似於{code:999,msg:"引數校驗失敗",data:null} 這種,但是原生的JSR303並不支援自定義的欄位,所以需要自定義校驗註解。針對這個問題我參考一些JSR303的資料,對其進行了一個定製擴充套件,以達到開發人員不需要關注捕捉和封裝返回資訊。

  

傳統的校驗做法 

        如下校驗如果一個實體裡面上百個欄位需要校驗的話,對於維護起來是一個很麻煩的事情,而且很多校驗可以通過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這個方法定義成抽象方法提供給不同的校驗器使用,避免其它校驗器寫重複的程式碼

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註解,這樣校驗器底層會幫我們按照順序依次處理

//順序註解
@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

總結

    以上就是本篇部落格涉及到技術點的所有程式碼,通過定製自己的校驗器以滿足公司業務場景,對於開發來說統一了規範,統一風格,對以後維護還是擴充套件都非常方便。 如果博文對你有幫助麻煩點個關注或者贊,謝