优雅的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