
【Spring Boot】如何优雅的验证参数环境引言谈参数校验分层问题参数基本合法性maven 依赖验证简单的参数类型验证复杂的参数类型多场景复杂类型验证环境jdk 1.8spring boot 2.1.3引言开发项目时很令人头疼的问题就是参数的验证问题通常的MVC架构下的三层controllerservicedao到底应该在哪一层进行参数的验证这是个问题到底应该怎样验证是使用传统的if-else这样的面条代码来进行验证还是使用其他方式来进行验证这也是问题。所以今天我们就来聊聊如何更简洁优雅的处理参数验证。谈参数校验分层问题通常来说我们的参数校验是分层的。也就是说我们会分别在controller与service层进行参数的校验那么这两层的参数校验有何不同如何区分呢其实如果开发项目的经验足够的话我们可以很明显的感觉到参数的校验简单地分为两种验证基本参数合法性对于第一种常见的比如验证参数是否为null啦验证字符串是否是空串啦验证数值是否在指定范围啦验证字符串长度等等。这一类逻辑比较简单的验证我们会在Controller这一层来完成。验证业务参数合法性而验证业务参数合法性我们通常会在Service层这也很合理。因为通常Service层的验证逻辑比较复杂比如必须这个用户先做某一操作后才能执行下一个操作先干了这些事才能干那些事等等…参数基本合法性因为Controller层的校验逻辑比较简单所以尽量交给代码在自动的完成。我们使用spring-boot的validation来做Controller层的参数校验。下面是maven依赖maven 依赖dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-validation/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency网上有很多童鞋会直接引入hibernate-validator进行数据校验其实spring-boot-starter-web已经为我们引入了hibernate-validator所以我们无需手动引入这个依赖。还有很多童鞋会直接使用hibernate-validator的注解来进行数据校验但是我们如果看源代码比如hibernate-validator的NotBlank这个注解的源代码就会发现这样一句话deprecateduse the standard{linkjavax.validation.constraints.NotBlank}constraint instead没错hibernate-validator的注解已经过期了因为现在java已经自带了数据校验注解比如NotBlankNotEmptyNull等等。这些注解都是JSR303的规范注解。没错这个注解是定义而校验的工作还是得交给数据校验的实现如hibernate-validator。所以以后不管引入什么样的数据校验实现我们只需要使用同一套的校验注解即可。验证简单的参数类型那么对于controller的验证如果请求处理方法的参数是如Integer、String等等这类比较简单的类型我们直接在方法的参数上加上对应的注解即可看下面这个例子RestControllerRequestMapping(/system/depart)publicclassSystemDepartController{AutowiredprivateSysDepartServicesysDepartService;// Controller 加上Validated注解后NotBlank才会生效GetMapping(/{id})publicResult?getById(PathVariable(id)NotBlank(message部门id不能为空)Stringid){SysDepartdepartsysDepartService.getById(id);if(departnull){thrownewNotFoundException(ExceptionEnum.DEPART_NOT_FOUNT);}returnResultUtil.ok(depart);}}在上面的代码中NotBlank中有一个message的字段这个message的值就是当参数验证不通过时的报错信息其他的校验注解的使用方式也基本一致。验证复杂的参数类型而如果我们要验证的是自定义的类比如什么entity类、model类等等那么这些校验注解就没法满足我们的需求了。此时我们就需要更加强大的校验功能来帮助我们实现更复杂的校验需求。比如我们有一个User的实体类我们要在后端验证用户名和密码不为空那么我们就可以这样写publicclassUser{privateStringid;NotBlank(message用户名不能为空,groups{Group.class})privateStringusername;NotBlank(message用户名不能为空,groups{Group.class})privateStringpassword;...//getter和setter就省略了}当然也可以对一个字段进行多种校验就像下面这样:publicclassUser{privateStringid;Email(message用户名必须是邮箱)NotBlank(message用户名不能为空,groupsMyGroup.class)privateStringusername;Pattern(message密码必须符合规定格式,regexp正则表达式...)NotBlank(message用户名不能为空,groupsMyGroup.class)privateStringpassword;...//getter和setter就省略了}大家注意到这次在校验注解中多了一个字段groups他的值为MyGroup.class,这又是哪里来的呢且慢我们先看看这次的Controller层的代码是怎么写的:RequestMapping(/user)RestControllerpublicclassUserController{PostMappingpublicStringsave(Validated(MyGroup.class)Useruser){...}}我们看到在save这个请求处理方法上有一个User类型的参数而且还在前面加了Validated注解这到底是干啥玩意儿的那么具体过程是这样的当客户端发来一个/user的POST类型的ajax请求我们后端开始就开始找对应的方法save进行处理在Spring MVC把JSON参数转换为User类型对象后一看,唉user变量前面居然有Validated注解值为MyGroup.class然后他就开始在User类里找了这么一找发现User类的两个字段上都有校验注解的groups字段的值是MyGroup.class 分别是username字段的NotBlank与password字段的NotBlank最后validater就会对这两个字段进行NotBlank校验。至于那个MyGroup.class,那个就是个自定义的类随便你甚至可以把java.lang.Object类设置为校验注解的groups字段的值就像这样NotBlank(message这个操作可不是一般人能做出来的groupsObject.class)多场景复杂类型验证当然有时候会有这样的需求执行保存操作的时候不需要验证id这个字段而在执行修改操作的时候却需要验证id这个字段是否不为空这种情况下我们该如何进行参数的验证呢其实很简单还是使用校验注解的groups字段看名字就知道这个字段是个数组也就是说一个校验注解可以在添加多个group那么这里的group我们就可以理解为业务场景。具体如何使用呢我先自定义了两个接口(普通类也可以)分别为SaveGroup和UpdateGroup随之对应着两个校验场景保存校验与更新校验。然后写了一个这样的User类publicclassUser{NotBlank(messageid不能为空,groupsUpdateGroup.class)privateStringid;Email(message用户名必须是邮箱)NotBlank(message用户名不能为空,groups{SaveGroup.class,UpdateGroup.class})privateStringusername;Pattern(message密码必须符合规定格式,regexp正则表达式...)NotBlank(message用户名不能为空,groups{SaveGroup.class,UpdateGroup.class})privateStringpassword;...//getter和setter就省略了}接着实现了Controller层的代码RequestMapping(/user)RestControllerpublicclassUserController{PostMappingpublicbooleansave(Validated(SaveGroup.class)Useruser){...}PostMappingpublicbooleanupdate(Validated(UpdateGroup.class)Useruser){...}}这样在执行save方法的时候User类中所有带有SaveGroup.class的注解的将会被执行校验而在执行update方法的时候User类中所有加UpdateGroup.class的注解将会被执行校验这样就成功的实现了在多场景下分别进行参数校验的效果。