springbootform-data传参数_使⽤SpringBoot进⾏优雅的数据
验证
JSR-303 规范
在程序进⾏数据处理之前,对数据进⾏准确性校验是我们必须要考虑的事情。尽早发现数据错误,不仅可以防⽌错误向核⼼业务逻辑蔓延,⽽且这种错误⾮常明显,容易发现解决。
JSR303 规范(Bean Validation 规范)为 JavaBean 验证定义了相应的元数据模型和 API。在应⽤程序中,通过使⽤ Bean Validation 或是你⾃⼰定义的 constraint,例如 @NotNull, @Max, @ZipCode , 就可以确保数据模型(JavaBean)的正确性。constraint 可以附加到字段,getter ⽅法,类或者接⼝上⾯。对于⼀些特定的需求,⽤户可以很容易的开发定制化的 constraint。Bean Validation 是⼀个运⾏时的数据验证框架,在验证之后验证的错误信息会被马上返回。
关于 JSR 303 – Bean Validation 规范,可以参考 官⽹
对于 JSR 303 规范,Hibernate Validator 对其进⾏了参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有⼀些附加的 constraint。如果想了解更多有关 Hibernate Validator 的信息,请查看 官⽹ 。
validation-api 内置的 constraint 清单
Hibernate Validator 附加的 constraint
Hibernate Validator 不同版本附加的 Constraint 可能不太⼀样,具体还需要你⾃⼰查看你使⽤版本。Hibernate 提供的 Constraint 在
org.straints 这个包下⾯。
⼀个 constraint 通常由 annotation 和相应的 constraint validator 组成,它们是⼀对多的关系。也就是说可以有多个 constraint
validator 对应⼀个 annotation。在运⾏时,Bean Validation 框架本⾝会根据被注释元素的类型来选择合适的 constraint validator 对数
据进⾏验证。
有些时候,在⽤户的应⽤中需要⼀些更复杂的 constraint。Bean Validation 提供扩展 constraint 的机制。可以通过两种⽅法去实现,⼀
种是组合现有的 constraint 来⽣成⼀个更复杂的 constraint,另外⼀种是开发⼀个全新的 constraint。
使⽤Spring Boot进⾏数据校验
Spring Validation 对 hibernate validation 进⾏了⼆次封装,可以让我们更加⽅便地使⽤数据校验功能。这边我们通过 Spring Boot 来
引⽤校验功能。
如果你⽤的 Spring Boot 版本⼩于 2.3.x,spring-boot-starter-web 会⾃动引⼊ hibernate-validator 的依赖。如果 Spring Boot 版本
⼤于 2.3.x,则需要⼿动引⼊依赖:
org.hibernate hibernate-validator 6.0.1.Final
直接参数校验
有时候接⼝的参数⽐较少,只有⼀个活着两个参数,这时候就没必要定义⼀个DTO来接收参数,可以直接接收参数。
@Validated@RestController@RequestMapping("/user")public class UserController { private static Logger logger = Logger(UserContro
下⾯是统⼀异常处理类
@RestControllerAdvicepublic class GlobalExceptionHandler { private static final Logger logger = Logger(GlobalExceptionHandler.class
调⽤结果
# 这⾥没有传userIdGET 127.0.0.1:9999/user/getUserHTTP/1.1 200 Content-Type: application/jsonTransfer-Encoding: chunkedDate: Sat, 14 Nov 2020 07:3实体类DTO校验
定义⼀个DTO
import org.straints.Range;import straints.NotEmpty;public class UserDTO { private Integer userId; @NotEmp
接收参数时使⽤@Validated进⾏校验
@PostMapping("/saveUser")@ResponseBody//注意:如果⽅法中的参数是对象类型,则必须要在参数对象前⾯添加 @Validatedpublic Response getUser(@Validat
统⼀异常处理
@ExceptionHandler(value = MethodArgumentNotValidException.class)public Response handle2(MethodArgumentNotValidException ex){ BindingResult
调⽤结果
### 创建⽤户POST 127.0.0.1:9999/user/saveUserContent-Type: application/json{ "name1": "程序员⾃由之路", "age": "18"}# 下⾯是返回结果{ "rtnCode": "1对Service层⽅法参数校验
个⼈不太喜欢这种校验⽅式,⼀半情况下调⽤service层⽅法的参数都需要在controller层校验好,不需要再校验⼀次。这边列举这个功能,
只是想说 Spring 也⽀持这个。
@Validated@Servicepublic class ValidatorService {private static final Logger logger = Logger(ValidatorService.class);public String show
分组校验
有时候对于不同的接⼝,需要对DTO进⾏不同的校验规则。还是以上⾯的UserDTO为列,另外⼀个接⼝可能不需要将age限制在18~50之
间,只需要⼤于18就可以了。
这样上⾯的校验规则就不适⽤了。分组校验就是来解决这个问题的,同⼀个DTO,不同的分组采⽤不同的校验策略。
public class UserDTO { public interface Default { } public interface Group1 { } private Integer userId; //注意:@Validated 注解中加上groups属性后,DTO 使⽤⽅式
@PostMapping("/saveUserGroup")@ResponseBody//注意:如果⽅法中的参数是对象类型,则必须要在参数对象前⾯添加 @Validated//进⾏分组校验,年龄满⾜⼤于使⽤Group1分组进⾏校验,因为DTO中,Group1分组对name属性没有校验,所以这个校验将不会⽣效。
分组校验的好处是可以对同⼀个DTO设置不同的校验规则,缺点就是对于每⼀个新的校验分组,都需要重新设置下这个分组下⾯每个属性的
校验规则。
分组校验还有⼀个按顺序校验功能。
考虑⼀种场景:⼀个bean有1个属性(假如说是attrA),这个属性上添加了3个约束(假如说是@NotNull、@NotEmpty、@NotBlank)。默
认情况下,validation-api对这3个约束的校验顺序是随机的。也就是说,可能先校验@NotNull,再校验@NotEmpty,最后校验
@NotBlank,也有可能先校验@NotBlank,再校验@NotEmpty,最后校验@NotNull。
那么,如果我们的需求是先校验@NotNull,再校验@NotBlank,最后校验@NotEmpty。@GroupSequence注解可以实现这个功能。
public class GroupSequenceDemoForm { @NotBlank(message = "⾄少包含⼀个⾮空字符", groups = {First.class}) @Size(min = 11, max = 11, message = "长度使⽤⽅式
// 先计算属于 First 组的约束,再计算属于 Second 组的约束@Validated(value = {GroupOrderedOne.class}) @RequestBody GroupSequenceDemoForm form
嵌套校验
前⾯的⽰例中,DTO类⾥⾯的字段都是基本数据类型和String等类型。
但是实际场景中,有可能某个字段也是⼀个对象,如果我们需要对这个对象⾥⾯的数据也进⾏校验,可以使⽤嵌套校验。
假如UserDTO中还⽤⼀个Job对象,⽐如下⾯的结构。需要注意的是,在job类的校验上⾯⼀定要加上@Valid注解。
public class UserDTO1 { private Integer userId; @NotEmpty private String name; @NotNull private Integer age; @Valid @NotNull private Jo
使⽤⽅式
@PostMapping("/saveUserWithJob")@ResponseBodypublic Response saveUserWithJob(@Validated @RequestBody UserDTO1 userDTO){ userDTO.s
测试结果
POST 127.0.0.1:9999/user/saveUserWithJobContent-Type: application/json{ "name": "程序员⾃由之路", "age": "16", "job": { "jobType": "1", "salary": "99
嵌套校验可以结合分组校验⼀起使⽤。还有就是嵌套集合校验会对集合⾥⾯的每⼀项都进⾏校验,例如List字段会对这个list⾥⾯的每⼀个
Job对象都进⾏校验。这个点在下⾯的@Valid和@Validated的区别章节有详细讲到。
集合校验
如果请求体直接传递了json数组给后台,并希望对数组中的每⼀项都进⾏参数校验。此时,如果我们直接使⽤java.util.Collection下的list
或者set来接收数据,参数校验并不会⽣效!我们可以使⽤⾃定义list集合来接收参数:
包装List类型,并声明@Valid注解
public class ValidationList implements List { // @Delegate是lombok注解 // 本来实现List接⼝需要实现⼀系列⽅法,使⽤这个注解可以委托给ArrayList实现 // @D 调⽤⽅法
@PostMapping("/batchSaveUser")@ResponseBodypublic Response batchSaveUser(@Validated(value = UserDTO.Default.class) @RequestBody Validatio
调⽤结果
Caused by: org.springframework.beans.NotReadablePropertyException: Invalid property 'list[1]' of bean class [com.csx.demo.spring.boot.dto.ValidationLis
会抛出NotReadablePropertyException异常,需要对这个异常做统⼀处理。这边代码就不贴了。
⾃定义校验器
在Spring中⾃定义校验器⾮常简单,分两步⾛。
⾃定义约束注解
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})@Retention(RUNTIME)@Documented@Constraint(validatedBy = {E
实现ConstraintValidator接⼝编写约束校验器
springboot框架的作用public class EncryptIdValidator implements ConstraintValidator { private static final Pattern PATTERN = Patternpile("^[a-fd]{32,256}$"); @Override
编程式校验
上⾯的⽰例都是基于注解来实现⾃动校验的,在某些情况下,我们可能希望以编程⽅式调⽤验证。这个时候可以注⼊
javax.validation.Validator对象,然后再调⽤其api。
@Autowiredprivate javax.validation.Validator globalValidator;// 编程式校验@PostMapping("/saveWithCodingValidate")public Result saveWithCodingValidate(@Req 快速失败(Fail Fast)配置
Spring Validation默认会校验完所有字段,然后才抛出异常。可以通过⼀些简单的配置,开启Fali Fast模式,⼀旦校验失败就⽴即返回。
@Beanpublic Validator validator() { ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) .configure() // 快速失败模式校验信息的国际化
Spring 的校验功能可以返回很友好的校验信息提⽰,⽽且这个信息⽀持国际化。
@Validated和@Valid的区别联系
⾸先,@Validated和@Valid都能实现基本的验证功能,也就是如果你是想验证⼀个参数是否为空,长度是否满⾜要求这些简单功能,使⽤
哪个注解都可以。
但是这两个注解在分组、注解作⽤的地⽅、嵌套验证等功能上两个有所不同。下⾯列下这两个注解主要的不同点。
@Valid注解是JSR303规范的注解,@Validated注解是Spring框架⾃带的注解;
@Valid不具有分组校验功能,@Validate具有分组校验功能;
@Valid可以⽤在⽅法、构造函数、⽅法参数和成员属性(字段)上,@Validated可以⽤在类型、⽅法和⽅法参数上。但是不能⽤在成员属性(字段)上,两者是否能⽤于成员属性(字段)上直接影响能否提供嵌套验证的功能;
@Valid加在成员属性上可以对成员属性进⾏嵌套验证,⽽@Validate不能加在成员属性上,所以不具备这个功能。
这边说明下,什么叫嵌套验证。
我们现在有个实体叫做Item:
public class Item { @NotNull(message = "id不能为空") @Min(value = 1, message = "id必须为正整数") private Long id; @NotNull(message = "props不能为空Item带有很多属性,属性⾥⾯有:pid、vid、pidName和vidName,如下所⽰:
public class Prop { @NotNull(message = "pid不能为空") @Min(value = 1, message = "pid必须为正整数") private Long pid; @NotNull(message = "vid不能为属性这个实体也有⾃⼰的验证机制,⽐如pid和vid不能为空,pidName和vidName不能为空等。
现在我们有个ItemController接受⼀个Item的⼊参,想要对Item进⾏验证,如下所⽰:
@RestControllerpublic class ItemController { @RequestMapping("/item/add") public void addItem(@Validated Item item, BindingResult bindingResult)
在上图中,如果Item实体的props属性不额外加注释,只有@NotNull和@Size,⽆论⼊参采⽤@Validated还是@Valid验证,Spring
Validation框架只会对Item的id和props做⾮空和数量验证,不会对props字段⾥的Prop实体进⾏字段验证,也就是@Validated和@Valid
加在⽅法参数前,都不会⾃动对参数进⾏嵌套验证。也就是说如果传的List中有Prop的pid为空或者是负数,⼊参验证不会检测出来。
为了能够进⾏嵌套验证,必须⼿动在Item实体的props字段上明确指出这个字段⾥⾯的实体也要进⾏验证。由于@Validated不能⽤在成员
属性(字段)上,但是@Valid能加在成员属性(字段)上,⽽且@Valid类注解上也说明了它⽀持嵌套验证功能,那么我们能够推断出:@Valid
加在⽅法参数时并不能够⾃动进⾏嵌套验证,⽽是⽤在需要嵌套验证类的相应字段上,来配合⽅法参数上@Validated或@Valid来进⾏嵌套
验证。
我们修改Item类如下所⽰:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论