優雅的java參數校驗


有參數傳遞的地方都少不了參數校驗。在web開發中,前端的參數校驗是為了用戶體驗,后端的參數校驗是為了安全。

試想一下,如果在controller層中沒有經過任何校驗的參數通過service層、dao層一路來到了數據庫就可能導致嚴重的后果,最好的結果是查不出數據,

嚴重一點就是報錯,如果這些沒有被校驗的參數中包含了惡意代碼,那就可能導致更嚴重的后果。

因此,對於請求參數,一般上都需要進行參數合法性校驗的,原先的寫法時一個個字段一個個去判斷,這種方式太不通用了,並且當參數較多的時候,代碼回顯的很臃腫,例如:

這是一個 bad practise !

所以java的JSR 303: Bean Validation規范就是解決這個問題的。

 

JSR 303只是個規范,並沒有具體的實現,目前通常都是由hibernate-validator進行統一參數校驗。

 

JSR303定義的基礎校驗類型:

Constraint
詳細信息
@Null 被注釋的元素必須為 null
@NotNull 被注釋的元素必須不為 null
@AssertTrue 被注釋的元素必須為 true
@AssertFalse 被注釋的元素必須為 false
@Min(value) 被注釋的元素必須是一個數字,其值必須大於等於指定的最小值
@Max(value) 被注釋的元素必須是一個數字,其值必須小於等於指定的最大值
@DecimalMin(value) 被注釋的元素必須是一個數字,其值必須大於等於指定的最小值
@DecimalMax(value) 被注釋的元素必須是一個數字,其值必須小於等於指定的最大值
@Size(max, min) 被注釋的元素的大小必須在指定的范圍內
@Digits (integer, fraction) 被注釋的元素必須是一個數字,其值必須在可接受的范圍內
@Past 被注釋的元素必須是一個過去的日期
@Future 被注釋的元素必須是一個將來的日期
@Pattern(value) 被注釋的元素必須符合指定的正則表達式

 

Hibernate Validator 中附加的 constraint :

Constraint
詳細信息
@Email 被注釋的元素必須是電子郵箱地址
@Length 被注釋的字符串的大小必須在指定的范圍內
@NotEmpty 被注釋的字符串的必須非空
@Range 被注釋的元素必須在合適的范圍內

 

下面通過一個例子來使用一下:

需要校驗的參數DTO
@Data
@ApiModel ( "回帖風控放行后發短消息提醒" )
public  class  PostCheckPassDTO {
     @NotNull (
         message =  "回帖用戶puid不能為空"
     )
     @ApiModelProperty ( "回帖用戶puid,必傳" )
     private  Long puid;
 
     @NotNull (
         message =  "回帖用戶username不能為空"
     )
     @ApiModelProperty ( "回帖用戶username,必傳" )
     private  String username;
 
     @NotNull (
         message =  "帖子id不能為空"
     )
     @ApiModelProperty ( "帖子id,必傳" )
     private  Long tid;
 
     @NotNull (
         message =  "回帖id不能為空"
     )
     @ApiModelProperty ( "回帖id" )
     private  Long pid;
 
     @NotNull (
         message =  "回帖內容不能為空"
     )
     @ApiModelProperty ( "回帖內容" )
     private  String content;
 
     @ApiModelProperty ( "回帖引用回帖id" )
     private  Long quote;
 
}

 

要注意加上@Valid注解開啟校驗

controller入口方法
@PostMapping ( "sendRemindForCheckPassPosts" )
public  DataResult<Boolean> sendRemindForCheckPassPosts( @Valid  @RequestBody   PostCheckPassDTO postCheckPassDTO){
     userBbsWriteService.sendRemindForCheckPassPosts(postCheckPassDTO);
     return  DataResult.ok( true );
}

 

這時候我們把參數里面的username去掉,請求一下接口,響應結果如下:

 


 

看到在校驗不通過時,返回的異常信息是不友好的,此時可利用統一異常處理,對校驗異常進行特殊處理,

特別說明下,對於異常處理類,共有以下幾種情況(被@RequestBody和@RequestParam注解的請求實體,校驗異常類是不同的),上代碼:

全局異常處理器
@Slf4j
@ControllerAdvice
public  class  GlobalExceptionHandler {
 
 
     /**
      * 異常處理
      * @param e
      * @return
      */
     @ResponseBody
     @ExceptionHandler (value = Exception. class )
     public  DataResult exceptionHandler(Exception e) {
         log.error( "GlobalExceptionHandler.exceptionHandler , 異常信息" ,e);
         /**cat error 上報*/
         Cat.logErrorWithCategory( "GlobalExceptionHandler" , "global error" ,e);
         return  DataResult.fail(e.getMessage());
     }
 
 
     /**
      * 業務異常
      * @param e
      * @return
      */
     @ResponseBody
     @ExceptionHandler (value = BplCommonException. class )
     public  DataResult bplCommonExceptionHandler(BplCommonException e) {
         log.warn( "" ,e);
         return  DataResult.fail(e.getMessage());
     }
 
     /**
      * 處理所有RequestBody注解參數驗證異常
      * @param e
      * @return
      */
     @ExceptionHandler (MethodArgumentNotValidException. class )
     @ResponseBody
     public  DataResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
         log.warn(e.getMessage());
         return  DataResult.fail(e.getBindingResult().getAllErrors().get( 0 ).getDefaultMessage());
     }
 
 
     /**
      * 處理所有RequestParam注解數據驗證異常
      * @param ex
      * @return
      */
     @ExceptionHandler (BindException. class )
     public  DataResult handleBindException(BindException ex) {
         FieldError fieldError = ex.getBindingResult().getFieldError();
         log.warn( "必填校驗異常:{}({})" , fieldError.getDefaultMessage(),fieldError.getField());
         return  DataResult.fail(ex.getBindingResult().getAllErrors().get( 0 ).getDefaultMessage());
     }
}

 

添加全局異常處理器后,這時我們再次請求接口,看看相應結果:

 

上面是最通用也是最常見的使用方式,可以解決絕大部分參數校驗的問題。還有剩下的小部分,我們可以通過自定義校驗注解的方式解決, 比如我們定義一個檢查版本號的注解:

自定義注解
@Documented
//指定注解的處理類
@Constraint (validatedBy = {VersionValidatorHandler. class  })
@Target ({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention (RUNTIME)
public  @interface  ConstantVersion {
 
    String message()  default  "{constraint.default.const.message}" ;
 
    Class<?>[] groups()  default  {};
 
    Class<?  extends  Payload>[] payload()  default  {};
 
    String value();
 
}

 

注解處理類   VersionValidatorHandler

注解處理類
public  class  VersionValidatorHandler  implements  ConstraintValidator<Constant, String> {
 
     private  String constant;
 
     @Override
     public  void  initialize(Constant constraintAnnotation) {
         //獲取設置的字段值
         this .constant = constraintAnnotation.value();
     }
 
     @Override
     public  boolean  isValid(String value, ConstraintValidatorContext context) {
         //判斷參數是否等於設置的字段值,返回結果
         return  constant.equals(value);
     }
 
}

注解的使用:

自定義注解使用
@ConstantVersion  (message =  "verson只能為1.0.0" ,value= "1.0.0" )
String version;


Spring Validation實現原理分析

 


免責聲明!

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



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