SpringMvc 请求参数验证(一)

doublespring注解

在开发中经常需要写一些字段校验的代码,比如字段非空、字段长度限制、邮箱格式、手机号码校验等等,@Validated注解就是是用于请求参数校验的。

验证分离原因

校验与业务逻辑关系并不是很大,在控制器里面应该尽量避免写参数校验逻辑,因为很多验证代码比较繁琐及重复劳动,会导致控制器内代码显得冗长,每次要看哪些参数校验是否完整,需要去翻阅验证逻辑代码

hibernate-validate 框架校验

hibernate validate 提供了一套比较完善、便捷的验证实现方式。使用 spring-boot 开发的项目可开箱即用,无需引用在添加 hibernate vaolidator 依赖,因为在 spring-boot-starter-web 包里面已经有 hibernate-validator 包了。

效验注解 可校验的数据类型 具体描述
@AssertTrue Boolean、boolean 属性值必须为true
@AssertFalse Boolean、boolean 属性值必须为false
@Null 基本数据类型除外 属性值必须为null
@NotNull 基本数据类型除外 属性值不能为null
@NotEmpty CharSequence、Conllection、Map、数组 属性值不能为null且字符串和集合长度不能为0(无法效验空字符串)
@NotBlank CharSequence 属性值不能为null且不能是空字符串
@Size CharSequence、Collection、Map、数组 属性值的长度必须在指定范围内
@Length CharSequence 属性值的长度必须在指定范围内
@Min Number、CharSequence(存储的是数字) 属性值必须大于指定的最小值
@Max Number、CharSequence(存储的数字) 属性值必须小于指定的最大值
@Range Number、CharSequence(存储的数字) 属性值的大小必须在指定的范围内
@Past Date、Calendar 属性值代表的日期时间不能大于当前日期时间
@Future Date、Calendar 属性值代表的日期时间不能小于当前日期时间
@Email CharSequence 属性值必须是合法的邮箱格式,可通过regexp属性自定义正则表达式
@Pattern CharSequence 属性值必须匹配正则表达式

简单验证

编写一个 DTO

import lombok.Getter; import lombok.Setter; import org.hibernate.validator.constraints.Length; import javax.validation.constraints.Email; @Getter @Setter public class Parent { @Length(max = 10, min = 1, message = "输入名字不规范") private String name; @Email(message = "非法邮箱") private String email; }

在 Constroller中添加校验

package com.isyxf.parameter.v2.controller; import com.isyxf.parameter.v2.dto.Parent; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class FirstController { /** * 测试1 * @return */ @PostMapping("/api/v2/one") public String one(@Validated @RequestBody Parent parent, BindingResult result) { // 通过 BindingResult 可以得到所有验证不通过的集合 if (result.hasErrors()) { for (ObjectError error : result.getAllErrors()) { System.out.println(error.getDefaultMessage()); } } System.out.println(result); return parent.getName(); } }

hibernate 的两种模式

上面的例子中会发现是一次性返回了所有验证不通过的集合,通常按顺序验证到第一个字段不符合验证要求时,就可以直接拒绝请求的。Hibernate Validator 有以下两种验证模式:

普通模式

默认是该模式,效验完所有的属性,然后返回所有的验证失败信息。

快速失败返回模式

该模式是只要有一个验证失败,则直接返回。

failFast: true 快速失败返回模式, false 普通模式

基本配置如下:

package com.isyxf.parameter.v2.configuration; import org.hibernate.validator.HibernateValidator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; /** * @author Y.jer * hibernate Validator为快速失败返回模式 */ @Configuration public class ValidatorConfiguration { @Bean public Validator validated() { ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) .configure() // failFast: true 快速失败返回模式, false 普通模式 .addProperty( "hibernate.validator.fail_fast", "true") .buildValidatorFactory(); Validator validator = validatorFactory.getValidator(); return validator; } }

为什么只能注解接收参数?

有时候我们会想,为什么 spring 框架接收参数非得通过注解来实现,不能像其他框架一样直接对应参数名?因为 spring 里面的方法不仅会接收路径上传递过来的参数,还有可能通过依赖注入的方法,注入其他的一些同名变量进来。

参数校验

参数校验是基于 JSR-303 规范来的

  1. url参数,都是get请求

    • 路径参数 (/api/user/{aaaa}),通过 @PathVarible 来接收
    • 查询参数(/api/test?foo=233)通过 @RequestParam 来接收
  2. body参数,都是post请求
    通过 @RequestBody 注解 加上 Map 或者类来接收
    Map是一种模糊的接收方式,不推荐使用Map 会涉及到拆箱和装箱的过程,影响性能

GET请求参数验证

在使用校验 bean 的方式,没有办法效验 RequestParam 的内容,一般在处理Get请求(或参数比较少)的时候,会使用如下的代码:

/** * 测试2 */ @GetMapping("/api/v2/two") public void tow(@RequestParam(name = "age") int age, @RequestParam(name = "address") String address) { System.out.println(age); System.out.println(address); }

需要学习: MethodValidationPostProcessor

在方法所在的 Controller 上加注解 @Validated

@RestController @Validated public class FirstController { /** * 测试2 */ @GetMapping("/api/v2/two") public void tow(@Range(min = 10, max = 20, message = "年龄只能在10~20") @RequestParam(name = "age") int age, @Length(min = 3,max = 7, message = "地址只能在3~7个字符") @RequestParam(name = "address") String address) { System.out.println(age); System.out.println(address); } }

POST 请求参数验证

在验证请求参数时,在 @RequestBody Parent parent 之间加注解 @Valid,然后在后面加上 BindindResult 即可;多个参数的,可以加多个 @Valid 和 BindingResult,如:

@PostMapping("/api/v2/one") public void one(@Validated @RequestBody Parent parent, BindingResult result, @Validated @RequestBody Parent2 parent2, BindingResult result2) { // 一些逻辑 }
1. 简单的body校验
@Getter @Setter public class Parent { @Length(max = 10, min = 1, message = "输入名字不规范") private String name; @Email(message = "非法邮箱") private String email; }
@RestController public class FirstController { /** * 测试1 * * @return */ @PostMapping("/api/v2/one") public void one(@Validated @RequestBody Parent parent, BindingResult result) { if (result.hasErrors()) { for (ObjectError error : result.getAllErrors()) { throw new RequestValidationException(288); } } } }
2. body级联参数验证

对象内部包含另一个对象作为属性,属性上加@Valid,可以验证作为属性的对象内部验证,如:

@Getter @Setter public class PersonDTO { @Length(min = 2, max = 10, message = "这是错误的") private String name; private Integer age; @Valid private SchoolDTO schoolDTO; } @Setter @Getter public class SchoolDTO { @Length(min = 3) private String schoolName; }

@Validated 注解和 @Valid 注解有什么区别?

  1. 两种者都是有一定的相似性,并不是互斥的,有时候 可以用 @Valid 代替 @Validated 的
  2. 只有级联校验才使用 @Valid ,其他情况如开启校验要使用 Validated 注解
  3. @Validated 注解表示开启 Spring 的效验机制,支持分组效验,声明在入参上。
  4. @Valid 注解表示开启 Hibernate 的效验机制,不支持分组效验,声明在入参上。

点击查看更详细区别

参考文章