SpringBoot實現通用的接口參數校驗


本文介紹基於Spring Boot和JDK8編寫一個AOP,結合自定義注解實現通用的接口參數校驗。

緣由

目前參數校驗常用的方法是在實體類上添加注解,但對於不同的方法,所應用的校驗規則也是不一樣的,例如有一個AccountVO實體:

publicclassAccountVO{privateStringname;//姓名privateIntegerage;//年齡}

假設存在這樣一個業務:用戶注冊時需要填寫姓名和年齡,用戶登陸時只需要填寫姓名就可以了。那么把校驗規則加在實體類上顯然就不合適了。

所以一直想實現一種方法級別的參數校驗,對於同一個實體參數,不同的方法可以應用不同的校驗規則,由此便誕生了這個工具,而且在日常工作中使用了很久。

介紹

先來看看使用的方式:

@ServicepublicclassTestImplimplementsITestService{@Override@Check({"name","age"})publicvoidtestValid(AccountVOvo){//...}}

其中方法上的@Check注解指明了參數AccountVO中的name、age屬性不能為空。除了非空校驗外,還支持大小判斷、是否等於等校驗:

@Check({"id>=8","name!=aaa","title<10"})

默認的錯誤信息會返回字段,錯誤原因和調用的方法,例如:

updateUserIdmustnotnullwhilecallingtestValididmust>=8whilecallingtestValidnamemust!=aaawhilecallingtestValid

也支持自定義錯誤返回信息:

@Check({"title<=8:標題字數不超過8個字,含標點符號"})publicvoidtestValid(TestPOpo){//...}

只需要在校驗規則后加上:,后面寫上自定義信息,就會替換默認的錯誤信息。

PS:
核心原理是通過反射獲取參數實體中的字段的值,然后根據規則進行校驗,
所以目前只支持含有一個參數的方法,並且參數不能是基礎類型。

使用

spring-boot中如何使用AOP這里不再贅述,主要介紹AOP中的核心代碼。Java:由淺入深揭開 AOP 實現原理

Maven 依賴

除了spring-boot依賴之外,需要的第三方依賴,不是核心的依賴,可以根據個人習慣取舍:

<!--用於字符串校驗--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.3.2</version></dependency><!--用於日志打印--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version></dependency>

自定義注解

importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.Target;importstaticjava.lang.annotation.RetentionPolicy.RUNTIME;/***參數校驗注解*/@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RUNTIME)public@interfaceCheck{//字段校驗規則,格式:字段名+校驗規則+冒號+錯誤信息,例如:id<10:ID必須少於10String[]value();}

核心代碼

通過切面攔截加上了@Check注解的接口方法,在方法執行前,執行參數校驗,如果存在錯誤信息,則直接返回:

@Around(value="@com.cipher.checker.Check")//這里要換成自定義注解的路徑publicObjectcheck(ProceedingJoinPointpoint)throwsThrowable{Objectobj;//參數校驗Stringmsg=doCheck(point);if(!StringUtils.isEmpty(msg)){//這里可以返回自己封裝的返回類thrownewIllegalArgumentException(msg);}obj=point.proceed();returnobj;}

核心的校驗方法在doCheck方法中,主要原理是獲取注解上指定的字段名稱和校驗規則,通過反射獲取參數實體中對應的字段的值,再進行校驗:

/***參數校驗**@parampointProceedingJoinPoint*@return錯誤信息*/privateStringdoCheck(ProceedingJoinPointpoint){//獲取方法參數值Object[]arguments=point.getArgs();//獲取方法Methodmethod=getMethod(point);StringmethodInfo=StringUtils.isEmpty(method.getName())?"":"whilecalling"+method.getName();Stringmsg="";if(isCheck(method,arguments)){Checkannotation=method.getAnnotation(Check.class);String[]fields=annotation.value();Objectvo=arguments[0];if(vo==null){msg="paramcannotbenull";}else{for(Stringfield:fields){//解析字段FieldInfoinfo=resolveField(field,methodInfo);//獲取字段的值Objectvalue=ReflectionUtil.invokeGetter(vo,info.field);//執行校驗規則BooleanisValid=info.optEnum.fun.apply(value,info.operatorNum);msg=isValid?msg:info.innerMsg;}}}returnmsg;}

可以看到主要的邏輯是:

解析字段 -> 獲取字段的值 -> 執行校驗規則

內部維護一個枚舉類,相關的校驗操作都在里面指定:

/***操作枚舉*/enumOperator{/***大於*/GREATER_THAN(">",CheckParamAspect::isGreaterThan),/***大於等於*/GREATER_THAN_EQUAL(">=",CheckParamAspect::isGreaterThanEqual),/***小於*/LESS_THAN("<",CheckParamAspect::isLessThan),/***小於等於*/LESS_THAN_EQUAL("<=",CheckParamAspect::isLessThanEqual),/***不等於*/NOT_EQUAL("!=",CheckParamAspect::isNotEqual),/***不為空*/NOT_NULL("notnull",CheckParamAspect::isNotNull);privateStringvalue;privateBiFunction<Object,String,Boolean>fun;Operator(Stringvalue,BiFunction<Object,String,Boolean>fun){this.value=value;this.fun=fun;}}

由於篇幅原因,這里就不一一展開所有的代碼,有興趣的朋友可以到以下地址獲取所有的源碼:

https://github.com/ciphermagic/java-learn/tree/master/sandbox/src/main/java/com/cipher/checker

TODO

  • 以Spring Boot Starter的方式封裝成獨立組件
  • 支持正則表達式驗證

以上實踐只做參考,可能會有一定的局限性


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM