快速上手:SpringBoot自定义请求参数校验

最近在工做中遇到写一些API,这些API的请求参数很是多,嵌套也很是复杂,若是参数的校验代码所有都手动去实现,写起来真的很是痛苦。正好Spring轮子里面有一个Validation,这里记录一下怎么使用,以及怎么自定义它的返回结果。

1、Bean Validation基本概念

Bean Validation是Java中的一项标准,它经过一些注解表达了对实体的限制规则。经过提出了一些API和扩展性的规范,这个规范是没有提供具体实现的,但愿可以Constrain once, validate everywhere。如今它已经发展到了2.0,兼容Java8。java

hibernate validation实现了Bean Validation标准,里面还增长了一些注解,在程序中引入它咱们就能够直接使用。spring

Spring MVC也支持Bean Validation,它对hibernate validation进行了二次封装,添加了自动校验,并将校验信息封装进了特定的BindingResult类中,在SpringBoot中咱们能够添加implementation('org.springframework.boot:spring-boot-starter-validation')引入这个库,实现对bean的校验功能。编程

2、基本用法

gradle dependencies以下:框架

快速上手:SpringBoot自定义请求参数校验

定义一个示例的Bean,例以下面的User.java。spring-boot

快速上手:SpringBoot自定义请求参数校验

在name属性上,添加@NotBlank和@Size(max=10)的注解,表示User对象的name属性不能为字符串且长度不能超过10个字符。测试

而后咱们暂时不添加任何多余的代码,直接写一个UserController对外提供一个RESTful的GET接口,注意接口的参数用到了@Validated注解。gradle

快速上手:SpringBoot自定义请求参数校验

启动SpringBoot程序,发一个测试请求看一下:spa

http://127.0.0.1:8080/validation/get?name=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaahibernate

返回的结果是,注意此时的HTTP STATUS CODE = 400: 3d

快速上手:SpringBoot自定义请求参数校验

此时已经能够实现参数的校验了,可是返回的结果不太友好,下面看一下怎么定制返回的消息。在定制返回结果前,先看下一下内置的校验注解有哪些,在这里我不一个个去贴了,写代码的时候根据须要进入到源码里面去看便可。

快速上手:SpringBoot自定义请求参数校验

早期Spring版本中,都是在Controller的方法中添加Errors/BindingResult参数,由Spring注入Errors/BindingResult对象,再在Controller中手写校验逻辑实现校验。新版本提供注解的方式(Controller上面bean加一个@Validated注解),将校验逻辑和Controller分离。

3、自定义校验

3.1 自定义注解

显然除了自带的NotNull、NotBlank、Size等注解,实际业务上还会须要特定的校验规则。

假设咱们有一个参数address,必须以Beijing开头,那咱们能够定义一个注解和一个自定义的Validator。

快速上手:SpringBoot自定义请求参数校验

而后在User.java中增长一个address属性,并给它加上上面这个自定义的注解,这里咱们定义了一个能够传入start参数的注解,表示应该以什么开头。

快速上手:SpringBoot自定义请求参数校验

除了定义能够做用于属性的注解外,其实还能够定义做用于class的注解(@Target({TYPE})),用于校验class的实例。

3.2 自定义Validator

第一步,实现一个Validator。(这种方法不须要咱们的bean里面有任何注解之类的东西)

快速上手:SpringBoot自定义请求参数校验

第二步,修改Controller代码,注入上面的UserValidator实例,并给Controller的方法参数加上@Validated注解,便可完成和前面自定义注解同样的校验功能。

快速上手:SpringBoot自定义请求参数校验

这个方法和自定义注解的区别在于不须要在Bean里面添加注解,而且能够更加灵活的把一个Bean里面全部的Field的校验代码都搬到一块儿,而不是每个属性都去加注解,若是校验的属性很是多,且默认注解的能力又不够的话,这种方式也是不错的,能够避免大量的自定义注解。

3.3 以编程的方式校验(手动)

这种方式能够算是原始的Hibernate-Validation的方式。直接看代码,这里有一个比较不一样的是,可使用Hibernate-Validation的Fail fast mode。由于前面的方式,都将全部的参数都验证完了,再把错误返回。有时咱们但愿遇到一个参数错误,就当即返回。

设置fast-fail为true能够达到这个目的。不过貌似不能再用@Validated注解方法参数了,而是要用ValidatorFactory建立Validator。

在实际开发中,没必要每次都编写代码建立Validator,能够采用@Configuration的方式建立,而后再@Autowired注入到每一个须要使用Validator的Controller当中。

快速上手:SpringBoot自定义请求参数校验

3.4 定义分组校验

有的时候,咱们会有两个不一样的接口,可是会使用到同一个Bean来做为VO(意思是两个接口的URI不一样,但参数中都用到了同一个Bean)。而在不一样的接口上,对Bean的校验需求可能不同,好比接口2须要校验studentId,而接口1不须要。那么此时就能够用到校验注解的分组groups。

快速上手:SpringBoot自定义请求参数校验

到这里,也能够带一嘴Valid和Validated注解的区别,其代码注释写着后者是对前者的一个扩展,支持了group分组的功能。

3.5 定制返回码和消息

第二节中定义了一个ServiceResponse,其实做为一个开放的API,不论用户传入任何参数,返回的结果都应该是预先定义好的格式,而且能够写明在接口文档中,即便发生了校验失败,应该返回一个包含错误码code(发生错误时通常大于0)和message字段。

快速上手:SpringBoot自定义请求参数校验

的结果,而HTTP STATUS CODE一直都是200。

为了实现这个目的,咱们加一个全局异常处理方法。

快速上手:SpringBoot自定义请求参数校验

在上面的方法中,咱们处理了BindException(非请求body参数,例如@RequestParam接收的)和MethodArgumentNotValidException(请求body里面的参数,例如@RequestBody接收的),这两类Exception里面都有一个BindingResult对象,它里面有一个包装成FieldError的List,保存着Bean对象出现错误的Field等信息。

取出它里面defaultMessage,放到统一的ServiceResponse返回便可实现返回码和消息的定制。因为消息内容是有注解默认的DefaultMessage决定的,为了按照自定义的描述返回,在Bean对象的注解上须要手动赋值为但愿返回的消息内容。

快速上手:SpringBoot自定义请求参数校验

这样当name参数长度超过10时,就会返回

快速上手:SpringBoot自定义请求参数校验

这里的FieldError fieldError = ex.getFieldError();只会随机返回一个出错的属性,若是Bean对象的多个属性都出错了,能够调用ex.getFieldErrors()来得到,这里也能够看到Spring Validation在参数校验时不会在第一次碰到参数错误时就返回,而是会校验完成全部的参数。

若是不想手动编程去校验,那么这里能够只读取一个随机的FieldError,返回它的错误消息便可。

3.6 更加细致的返回码和消息

其实还有一种比较典型的自定义返回,就是错误码(code)和消息(message)是一一对应的,好比:

  • 51001:字符串长度过长
  • 51002:参数取值过大

这种状况比较特殊,通常当参数错误的时候,会返回一个总体的参数错误的错误码,而后携带参数的错误信息。但有时,业务上就要不一样的参数错误,既要错误码不一样,错误信息也要不一样。我想了下,有两种思路。

  • 第一种:经过message同时包含错误码和错误信息,在全局异常捕获方法中,再把它们拆开。
  • 第二种:手动校验,抛出自定义的Exception(里面带有code、message)。手动校验这里,若是每个Controller都去写一遍,确实比较费劲,能够结合AOP来实现,或者抽出一个基类BaseController的方式。

4、小结

其实在实际的工做中,确定还有更复杂的校验逻辑,可是不必定非要都用框架去实现,框架里面的实现(好比注解)应该是一个比较简单通用的校验,可以达到复用,减小重复的劳动。而更加复杂的逻辑校验,必定是存在具体业务当中的,最好是在业务代码里面实现。

还有一点须要注意,Spring Validation的isValid方法,若是返回false,那么Controller再也不会被调用,而是直接返回。若是你在Controller上面加了AOP进行接口调用统计的话,可能会漏掉。这个时候,咱们不该该让Controller不调用,建议这种状况在AOP里面对Controller的参数切面进行校验后,抛出统一的业务异常。

相关文章
相关标签/搜索