SpringBoot整合Hibernate Validator实现参数校验功能
1.引入Hibernate Validator依赖
<!-- 校验器 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
2.创建校验配置类
Hibernate Validator有两种验证模式:
(1)普通模式(设置hibernate.validator.fail_fast为false)——会校验完所有的属性,然后返回所有的验证失败信息。
(2)快速模式(设置hibernate.validator.fail_fast为true)——校验过程中,只要有一个失败,则返回验证失败信息。
@Configuration
public class ValidatorConfig {
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.addProperty( "hibernate.validator.fail_fast", "true" ) //快速模式
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}
3.在实体类中成员变量中添加校验注解
常见的校验注解如下:
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
实体类中部分校验注解配置如下:
@Data
public class UserModel implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1906142335911461001L;
private String id;
@NotNull(message = "姓名不能为空")
@Length(min = 1, max = 20, message = "姓名为1到20个字符之间")
@Pattern(regexp="^[\\u4e00-\\u9fa5]{0,}$", message="姓名为汉字字符")
private String name;
@Pattern(regexp="^[A-Za-z]+$", message="英文名称只能包含字母")
@Length(min = 1, max = 20, message = "英文名称为1到20个字符之间")
private String englishName;
@Range(min = 1, max = 200, message = "年龄范围为1到200")
private Integer age;
@NotBlank(message="性别不能为空")
@Pattern(regexp="^(0|1)$", message="性别不正确")
private String gender;
@AssertFalse(message = "必须为false")
private Boolean isFalse;
@AssertTrue(message = "必须为true")
private Boolean isTrue;
@Range(min = 1, max = 500, message = "体重范围为1到500kg")
private Float weight;
@Range(min = 1, max = 300, message = "年龄范围为1到300cm")
private Float tall;
@NotNull(message = "身份证不能为空")
@Pattern(regexp="^(\\d{18,18}|\\d{15,15}|(\\d{17,17}[x|X]))$", message="身份证格式错误")
private String cardId;
@Pattern(regexp="^((25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))\\.){3}(25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))$", message="ip格式不正确")
private String ip;
@Length(min = 0, max = 200, message = "工作内容不能超过200")
@Pattern(regexp="^http[s]*://[^\\s]*$", message="个人网站地址格式不正确")
private String website;
@Length(min = 4, max = 20, message = "qq号码不能超过20")
@Pattern(regexp="^[1-9][0-9]{4,}$", message="qq号是从1000开始")
private String qqNum;
@NotBlank(message="userName不能为空")
@Length(max=6, min=3, message="userName最小3位,最大6位")
private String userName;
@NotBlank(message="密码不能为空")
@Pattern(regexp="^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message="密码必须是8~10位数字和字母的组合")
private String password;
@Pattern(regexp="^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$", message="手机号格式不正确")
private String cellphone;
@Pattern(regexp="^\\d{3}-\\d{8}|\\d{4}-\\d{7}$", message="固定电话格式不正确")
private String telephone;
@Email(message="邮箱格式不正确")
@Pattern(regexp="^([\\w-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([\\w-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$", message="邮箱格式不正确")
private String email;
@Past
private Date bornDate;
@Future
private Date future;
@Length(min = 0, max = 200, message = "地址不能超过200")
private String address;
@Pattern(regexp="^[1-9]\\d{5}(?!\\d)$", message="邮政编码格式不正确")
private String postCode;
@Valid
private UserModel father;
@Valid
private UserModel mother;
@Length(min = 0, max = 200, message = "工作内容不能超过200")
private String work;
}
其中在“private UserModel father”上配置的“@Valid”作用是可以校验属性“father”对象内部的所有属性,即级联校验。
上述实体成员变量中有很多都使用了一个校验注解——@Pattern,在该注解上用户可以自定义正则表达式来实现字符串格式校验,本示例提供了汉字、英文、性别、ip、url、qq号码、手机号、固定电话、邮箱、邮政编码等模式。
4.在Controller中方法中添加@Valid注解,处理校验错误信息
一般对于校验错误信息处理有两种方式:
一种是在Controller中方法参数需要校验的实体上添加@Valid注解,方法再增加一个参数——“BindingResult result”,在方法中可以根据result对象获取校验信息,如下所示:
@PostMapping("/testPost")
public Object testPost(@RequestBody @Valid UserModel userModel, BindingResult result){
StringBuilder sb = new StringBuilder();
if(result.hasErrors()){
for(ObjectError error:result.getAllErrors()){
sb.append(error.getDefaultMessage());
sb.append(";");
}
return sb.toString();
}
return userModel;
}
另外一种是只需在Controller中方法参数需要校验的实体上添加@Valid注解:
@PostMapping("/testPost2")
public Object testPost2(@RequestBody @Valid UserModel userModel){
...
return userModel;
}
但需要在启动类所在包下创建控制器切面类,并创建方法捕捉MethodArgumentNotValidException异常,在该方法中统一处理校验失败信息:
@RestControllerAdvice
@Component
public class GlobalExceptionAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public WrappedResult validException(MethodArgumentNotValidException e) {
//验证post请求的参数合法性
MethodArgumentNotValidException notValidException = e;
BindingResult result = notValidException.getBindingResult();
StringBuilder sb = new StringBuilder();
if(result.hasErrors()){
for(ObjectError error:result.getAllErrors()){
sb.append(error.getDefaultMessage());
sb.append(";");
}
}
String msg = sb.toString();
//WrappedResult是自定义返回结果包装类,用户可以使用自己的工具类
return WrappedResult.failedWrappedResult(msg);
}
}
该方法主要的操作是从捕获的MethodArgumentNotValidException 异常对象中获取BindingResult 对象,再从BindingResult 对象中获取错误信息列表,遍历改列表,通过StringBuilder 对象拼接错误信息,最后通过WrappedResult包装成WrappedResult对象返回。
5.测试
本测试使用postman进行测试,首先启动示例工程,访问http://localhost:8080/testPost2,即测试使用切面控制器的校验处理方式:
测试数据:
{
"id":"asfda546a6565565e6s568d79",
"name":"张小三",
"age":29,
"cardId":"wererewrewttwwte545",
"gender":"0",
"isFalse":false,
"isTrue":true,
"weight":72.3,
"tall":172,
"ip":"192.168.1.101",
"website":"http://www.zhangxiaosan.cn",
"qqNum":"694434544",
"userName":"tianyamingyuedao",
"password":"sanquanbuliugen",
"cellphone":"13845442123",
"telephone":"010-43564234",
"email":"2345435345@qq.com",
"bornDate":"1992-07-17",
"future":"2088-07-17",
"address":"北京市西城区xxxx",
"postCode":"100010",
"work":"xxx公司",
"father":{
"id":"asfda546a6565565e6s568d79",
"name":"张老三",
"age":61,
"cardId":"wererewrewttwwte545",
"gender":"0",
"isFalse":false,
"isTrue":true,
"weight":72.3,
"tall":172,
"ip":"192.168.1.101",
"website":"http://www.张老三.cn",
"qqNum":"694434544",
"userName":"zhanglaosan",
"password":"sanquanbuliugen",
"cellphone":"13845442123",
"telephone":"010-43564234",
"email":"6944345@qq.com",
"bornDate":"1960-07-17",
"future":"2060-07-17",
"address":"北京市西城区xxxx",
"postCode":"100010"
},
"mother":{
"id":"asfda546a6565565e6s568d79",
"name":"陈小六",
"age":57,
"cardId":"wererewrewttwwte545",
"gender":"1",
"isFalse":false,
"isTrue":true,
"weight":72.3,
"tall":172,
"ip":"192.168.1.101",
"website":"http://www.chenxiaoliu.cn",
"qqNum":"694434544",
"userName":"chenxiaoliu",
"password":"sanquanbuliugen",
"cellphone":"118245442123",
"telephone":"010-43564234",
"email":"6944345@qq.com",
"bornDate":"1964-07-17",
"future":"2060-07-17",
"address":"北京市西城区xxxx",
"postCode":"100010"
}
}
(1)当校验配置类中设置hibernate.validator.fail_fast为true,即快速模式时,结果如下图:
可以看到提示信息中只有一个提示——“身份证格式错误”。
(2)当校验配置类中设置hibernate.validator.fail_fast为false,即普通模式时,结果如下图:
可以看到提示信息中有很多个提示,说明普通模式生效:还有一些一样的提示,因为测试数据中一些属性数据是的错误是一样的,说明级联校验成功。
6.高级用法
Hibernate Validator高级用法详见:
https://docs.jboss.org/hibernate/validator/6.1/reference/en-US/html_single/#validator-gettingstarted