请不要在Java开发中再使用判断进行参数校验了
3.JSR303校验规范及其实现为了解决上面的痛点 , 将验证逻辑与相应的领域模型进行绑定是十分有必要的 。 为此产生了JSR303–BeanValidation规范 。 HibernateValidator是JSR-303的参考实现 , 它提供了JSR303规范中所有的约束(constraint)的实现 , 同时也增加了一些扩展 。
HibernateValidator提供的常用的约束注解
org.springframework.bootspring-boot-starter-validation一种可以实现接口来定制Validator , 一种是使用约束注解 。 胖哥觉得注解可以满足绝大部分的需求 , 所以建议使用注解来进行数据校验 。 而且注解更加灵活 , 控制的粒度也更加细 。 接下来我们来学习如何使用注解进行数据校验 。
4.1约束注解的基本使用我们对需要校验的方法入参进行注解约束标记 , 例子如下:
@DatapublicclassStudent{@NotBlank(message="姓名必须填")privateStringname;@NotNull(message="年龄必须填写")@Range(min=1,max=50,message="年龄取值范围1-50")privateIntegerage;@NotEmpty(message="成绩必填")privateListscores;}POST请求然后定义一个POST请求的SpringMVC接口:
?@RestController@RequestMapping("/student")publicclassStudentController{?@PostMapping("/add")publicRest>addStudent(@Valid@RequestBodyStudentstudent){returnRestBody.okData(student);}}通过对addStudent方法入参添加@Valid来启用参数校验 。 当使用下面数据进行请求将会抛出MethodArgumentNotValidException异常 , 提示age范围超出1-50 。
POST/student/addHTTP/1.1Host:localhost:8888Content-Type:application/json?{"name":"felord.cn","age":77,"scores":[55]}GET请求如法炮制 , 我们定义一个GET请求的接口:
@GetMapping("/get")publicRest>getStudent(@ValidStudentstudent){returnRestBody.okData(student);}使用下面的请求可以正确对学生分数scores进行了校验 , 但是抛出的并不是MethodArgumentNotValidException异常 , 而是BindException异常 。 这和使用@RequestBody注解有关系 , 这对我们后面的统一处理非常十分重要 。
GET/student/get?name=felord.cn&age=12HTTP/1.1Host:localhost:8888自定义注解可能有些同学注意到上面的年龄我进行了这样的标记:
@NotNull(message="年龄必须填写")@Range(min=1,max=50,message="年龄取值范围1-50")privateIntegerage;这是因为@Range不会去校验为空的情况 , 它只处理非空的时候是否符合范围约束 。 所以要用多个注解来约束 。 如果我们某些场景需要重复的捆绑多个注解来使用时 , 可以使用自定义注解将它们封装起来组合使用 , 下面这个注解就是将@NotNull和@Range进行了组合 , 你可以仿一个出来用用看 。
importorg.hibernate.validator.constraints.Range;?importjavax.validation.Constraint;importjavax.validation.Payload;importjavax.validation.ReportAsSingleViolation;importjavax.validation.constraints.NotNull;importjavax.validation.constraintvalidation.SupportedValidationTarget;importjavax.validation.constraintvalidation.ValidationTarget;importjava.lang.annotation.*;?/***@authora*@since17:31**/@Constraint(validatedBy={})@SupportedValidationTarget({ValidationTarget.ANNOTATED_ELEMENT})@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD,ElementType.FIELD,ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.PARAMETER,ElementType.TYPE_USE})@NotNull@Range(min=1,max=50)@Documented@ReportAsSingleViolationpublic@interfaceAge{//message必须有Stringmessage()default"年龄必须填写 , 且范围为1-50";?//可选Class>[]groups()default{};?//可选ClassextendsPayload>[]payload()default{};}还有一种情况 , 我们在后台定义了枚举值来进行状态的流转 , 也是需要校验的 , 比如我们定义了颜色枚举:
publicenumColors{RED,YELLOW,BLUE}我们希望入参不能超出Colors的范围["RED","YELLOW","BLUE"] , 这就需要实现ConstraintValidator接口来定义一个颜色约束了 , 其中泛型A为自定义的约束注解 , 泛型T为入参的类型 , 这里使用字符串,然后我们的实现如下:
/***@authorfelord.cn*@since17:57**/publicclassColorConstraintValidatorimplementsConstraintValidator{privatestaticfinalSetCOLOR_CONSTRAINTS=newHashSet<>();@Overridepublicvoidinitialize(ColorconstraintAnnotation){Colors[]value=https://pcff.toutiao.jxnews.com.cn/p/20200809/constraintAnnotation.value();Listlist=Arrays.stream(value).map(Enum::name).collect(Collectors.toList());COLOR_CONSTRAINTS.addAll(list);}@OverridepublicbooleanisValid(Stringvalue,ConstraintValidatorContextcontext){returnCOLOR_CONSTRAINTS.contains(value);}}然后声明对应的约束注解@Color , 需要在元注解@Constraint中指明使用上面定义好的处理类ColorConstraintValidator进行校验 。
/***@authorfelord.cn*@since17:55**/@Constraint(validatedBy=ColorConstraintValidator.class)@Documented@Target({ElementType.METHOD,ElementType.FIELD,ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.PARAMETER,ElementType.TYPE_USE})@Retention(RetentionPolicy.RUNTIME)public@interfaceColor{//错误提示信息Stringmessage()default"颜色不符合规格";Class>[]groups()default{};ClassextendsPayload>[]payload()default{};//约束的类型Colors[]value();}然后我们来试一下 , 先对参数进行约束:
@DatapublicclassParam{@Color({Colors.BLUE,Colors.YELLOW})privateStringcolor;}接口跟上面几个一样 , 调用下面的接口将抛出BindException异常:
GET/student/color?color=CAYHTTP/1.1Host:localhost:8888当我们把参数color赋值为BLUE或者YELLOW后 , 能够成功得到响应 。
4.2常见问题在实际使用起来我们会遇到一些问题 , 这里总结了一些常见的问题和处理方式 。
检验基础类型不生效的问题上面为了校验颜色我们声明了一个Param对象来包装唯一的字符串参数color , 为什么不直接使用下面的方式定义呢?
@GetMapping("/color")publicRest>color(@Valid@Color({Colors.BLUE,Colors.YELLOW})Stringcolor){returnRestBody.okData(color);}或者使用路径变量:
@GetMapping("/rest/{color}")publicRest>rest(@Valid@Color({Colors.BLUE,Colors.YELLOW})@PathVariableStringcolor){returnRestBody.okData(color);}上面两种方式是不会生效的 。 不信你可以试一试 , 起码在SpringBoot2.3.1.RELEASE是不会直接生效的 。
使以上两种生效的方法是在类上添加@Validated注解 。 注意一定要添加到方法所在的类上才行 。 这时候校验失败会抛出ConstraintViolationException异常 。
集合类型参数中的元素不生效的问题就像下面的写法 , 方法的参数为集合时 , 如何检验元素的约束呢?
/***集合类型参数元素.**@paramstudentthestudent*@returntherest*/@PostMapping("/batchadd")publicRest>batchAddStudent(@Valid@RequestBodyListstudent){returnRestBody.okData(student);}同样是在类上添加@Validated注解 。 注意一定要添加到方法所在的类上才行 。 这时候校验失败会抛出ConstraintViolationException异常 。
嵌套校验不生效嵌套的结构如何校验呢?打个比方 , 如果我们在学生类Student中添加了其所属的学校信息School并希望对School的属性进行校验 。
@DatapublicclassStudent{@NotBlank(message="姓名必须填")privateStringname;@AgeprivateIntegerage;@NotEmpty(message="成绩必填")privateListscores;@NotNull(message="学校不能为空")privateSchoolschool;}@DatapublicclassSchool{@NotBlank(message="学校名称不能为空")privateStringname;@Min(value=https://pcff.toutiao.jxnews.com.cn/p/20200809/0,message="校龄大于0")privateIntegerage;}当GET请求时正常校验了School的属性 , 但是POST请求却无法对School的属性进行校验 。 这时我们只需要在该属性上加上@Valid注解即可 。
@DatapublicclassStudent{@NotBlank(message="姓名必须填")privateStringname;@AgeprivateIntegerage;@NotEmpty(message="成绩必填")privateListscores;@Valid@NotNull(message="学校不能为空")privateSchoolschool;}每加一层嵌套都需要加一层@Valid注解 。 通常在校验对象属性时 , @NotNull、@NotEmpty和@Valid配合才能起到校验效果 。
如果你有其它问题可以通过felord.cn联系到我探讨 。
5.总结【请不要在Java开发中再使用判断进行参数校验了】通过校验框架我们可以专心于业务开发 , 本文对HibernateValidator的使用和一些常见问题进行了梳理 。 我们可以通过SpringBoot统一异常处理来解决参数校验的异常信息的提示问题 。 具体可以通过关注:码农小胖哥回复valid获取相关DEMO 。 如果觉得本文对你有用 , 请点赞、转发、在看给胖哥以创作动力 。
推荐阅读
- 拜登|嘲笑拜登将在地下室接受民主党提名,特朗普本周要在全美国巡回表演抢镜头
- Java|软件开发平台之争:NET VS Java,谁是更好的选择?
- Java|办公小技巧:超凡业绩 从完美的KPI仪表板开始
- 孙悟空都请不动的神仙是谁?他的实力高于元始天尊,连玉帝都怕他
- 小艾米文化|孙悟空都请不动的神仙是谁?他的实力高于元始天尊,连玉帝都怕他
- 昨天忘了笑|想要在节假日精力充沛,制作这道饮品,让你拥有工作日的效率
- 快乐大本营|当年录《快本》挨饿没人理睬,如今再也请不动了,早已从青铜变王者
- 阿里巴巴|学Java前,我一直以为阿里巴巴就是卖货的
- 马歇尔·比林斯利|美国要在我们“家门口”部署中程导弹?
- Java|面向对象之构造方法、成员变量 Java记录07
