当前位置: 首页 > 科技观察

SpringBoot参数校验和分组校验的使用

时间:2023-03-12 19:49:58 科技观察

前言web开发的一件烦人的事情就是对前端输入的参数进行校验。基本上每个接口都需要对参数进行校验,比如一些非空Validation,格式校验等,参数少的话好办,但是参数多了就会出现大量的if-else代码中的语句。虽然使用这种方法简单直接,但也有缺点。一来会降低开发效率,因为我们需要验证的参数会存在很多地方,不同的地方会重复验证,二来会降低代码的可读性,因为业务代码中夹杂了太多多余的工作代码。所以我们可以使用验证器组件来代替我们不必要的编码操作。这篇文章是根据验证者的介绍信息,也是结合我在项目中的实际经验总结出来的,希望对大家有所帮助。1什么是validatorBeanValidation是Java定义的一套基于注解的数据验证规范。从JSR303的1.0版本升级到JSR349的1.1版本,再到JSR380的2.0版本(2.0于2017.08完成)。已经出现了三个版本。需要注意的是,JSR只是一个标准,规定了验证注解的一些规范,但并未实现,比如@Null、@NotNull、@Pattern等,位于javax.validation.constraints包下.hibernatevalidator是这个规范的实现,并添加了一些其他的验证注解,比如@NotBlank、@NotEmpty、@Length等,位于org.hibernate.validator.constraints包下。如果我们的项目使用的是SpringBoot,那么在spring-boot-starter-web中已经集成了hibernatevalidator框架,所以不需要再添加其他依赖。如果不是SpringBoot项目,需要添加如下依赖。org.hibernate.validatorhibernate-validator6.0.8.Final二个注解介绍1验证器内置注解注解说明@Null被注解的元素必须为空@NotNull被注解的元素不能为空@AssertTrue被注解的元素必须为真@AssertFalse被注解的元素必须为假@Min(value)被注解的元素必须为数字,其值必须大于等于指定的最小值@Max(value)被注解的元素必须是数字,其值必须小于等于指定的最大值@DecimalMin(value)被注解的元素必须是数字,anditsvaluemustbegreaterthan等于指定的最小值@DecimalMax(value)被注解的元素必须是一个数字,其值必须小于等于指定的最大值@Size(max,min)被注解的元素的大小元素必须在指定范围内@Digits(整数,分数on)被注解的元素必须是一个数字,其值必须在可接受的范围内@Past被注解的元素必须是过去的日期@Future被注解的元素必须是未来的日期@Pattern(value)被注解的元素必须符合指定的正则表达式。hibernatevalidator扩展中定义了如下注解:注解@NotBlank注解元素不能为null,长度必须大于0,只能用于注解字符串@Email注解元素必须是邮箱地址@Length(min=,max=)被注解的字符串大小必须在指定范围内@NotEmpty被注解的元素的值不为null且不为空,支持字符串、集合、Maps数组类型注解的元素@范围必须在指定范围内。3.使用起来比较简单。它们都是以注解的方式使用的。具体分为单参数验证和对象参数验证。单参数验证是控制器接口。根据单参数接收前端传值,没有封装对象接收,如果有封装对象,则为对象参数校验。1单参数验证单参数验证只需要在参数前加上注解即可,如下图:publicResultdeleteUser(@NotNull(message="idcannotbeempty")Longid){//dosomething}但是要付出一点注意,如果使用单参数校验,必须在controller类中添加@Validated注解,如下所示:@RestController@RequestMapping("/user")@Validated//单参数校验需要注解publicclassUserController{//dosomething}2objectParametervalidation使用对象参数校验时,需要先在对象的校验属性上添加注解,然后在Controller方法的对象参数前添加@Validated注解,如下所示:publicResultaddUser(@ValidatedUserAOuserAo){//dosomething}publicclassUserAO{@NotBlankprivateStringname;@NotNullprivateIntegerage;...}注解分组在对象参数验证的场景中,有一个特殊的场景,同一个参数对象在不同的??场景下有不同的校验规则。比如创建对象时不需要传入id字段(id字段为主键,系统生成,用户不指定),但是修改对象时必须传入id字段目的。在这种情况下,需要对注释进行分组。1)组件有一个默认组Default.class,所以我们可以再创建一个组UpdateAction.class,如下所示:publicinterfaceUpdateAction{}2)在参数类中需要校验的属性的注解中添加groups属性:publicclassUserAO{@NotNull(groups=UpdateAction.class,message="idcannotbeempty")privateLongid;@NotBlankprivateStringname;@NotNullprivateIntegerage;...}如上图,表示只在UpdateAction组下勾选id字段.默认情况下,名称字段和年龄字段将被选中。然后,在controller的方法中,在@Validated注解中指定使用何种场景即可。如果不指定,则表示使用Default.class,如果使用其他组,则需要显示和指定。以下代码表示在addUser()接口中根据默认情况进行参数校验,在updateUser()接口中根据默认情况和UpdateAction分组联合校验参数。publicResultaddUser(@ValidatedUserAOuserAo){//dosomething}publicResultupdateUser(@Validated({Default.class,UpdateAction.class})UserAOuserAo){//dosomething}对象嵌套如果参数对象中嵌套有对象属性需要校验,嵌套的对象属性也需要校验,那么需要在对象属性上加上@Valid注解。publicclassUserAO{@NotNull(groups=UpdateAction.class,message="idcannotbeempty")privateLongid;@NotBlankprivateStringname;@NotNullprivateIntegerage;@ValidprivatePhonephone;……}publicclassPhone{@NotBlankprivateStringoperatorType;@NotBlankprivateStringphoneNum;}之后会抛出异常验证失败。我们只需要在全局异常处理类中捕获参数验证失败的异常,然后将错误信息添加到返回值中即可。捕获异常的方法如下,返回值Result是我们系统自定义的返回值类。@RestControllerAdvice(basePackages={"com.alibaba.dc.controller","com.alibaba.dc.service"})publicclassGlobalExceptionHandler{@ExceptionHandler(value={Throwable.class})ResulthandleException(Throwablee,HttpServletRequestrequest){//异常处理}}需要注意的是参数丢失抛出的异常是MissingServletRequestParameterException,单参数校验失败抛出的异常是ConstraintViolationException,get请求的对象参数校验失败抛出的异常是BindException,异常post请求抛出的对象参数校验失败后抛出的异常为MethodArgumentNotValidException。不同的异常对象结构不同,提取异常信息的方法也不同。如下图所示:1)MissingServletRequestParameterExceptionif(einstanceofMissingServletRequestParameterException){Resultresult=Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);Stringmsg=MessageFormat.format("缺少参数{0}",((MissingServletRequestParameterException)e).getParameterName());result...).getConstraintViolations();if(CollectionUtils.isNotEmpty(sets)){StringBuildersb=newStringBuilder();sets.forEach(error->{if(errorinstanceofFieldError){sb.append(((FieldError)error).getField()).append(":");}sb.append(error.getMessage()).append(";");});Stringmsg=sb.toString();msg=StringUtils.substring(msg,0,msg.length()-1);result.setMessage(msg);}returnresult;}3)BindException异常if(einstanceofBindException){//get请求的对象参考校数异常Resultresult=Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);Listerrors=((BindException)e).getBindingResult().getAllErrors();Stringmsg=getValidExceptionMsg(errors);if(StringUtils.isNotBlank(msg)){result.setMessage(msg);}returnresult;}privateStringgetValidExceptionMsg(Listerrors){if(CollectionUtils.isNotEmpty(errors)){StringBuildersb=newStringBuilder();错误。forEach(error->{if(errorinstanceofFieldError){sb.append(((FieldError)error).getField()).append(":");}sb.append(error.getDefaultMessage()).append(";");});Stringmsg=sb.toString();msg=StringUtils.substring(msg,0,msg.length()-1);returnmsg;}returnnull;}4)MethodArgumentNotValidException异常if(einstanceofMethodArgumentNotValidException){//post请求的对象参考校数异常Resultresult=Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);Listerrors=((MethodArgumentNotValidException)e).getBindingResult().getAllErrors();Stringmsg=getValidExceptionMsg(errors);if(StringUtils.isNotBlank(msg)){result.setMessage(msg);}returnresult;}【本文为专栏作者“阿里巴巴官方技术”原创稿件,转载请注明出处】联系原作者】点此阅读该作者更多好文