如何使用Bean Validation 解决业务中参数校验
前言
在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等,写这些与业务逻辑关系不大的代码个人感觉有点麻烦:
验证代码繁琐,重复劳动
方法内代码显得冗长
每次要看哪些参数验证是否完整,需要去翻阅验证逻辑代码
叙述
Bean Validation是一个通过配置注解来验证参数的框架,它包含两部分Bean Validation API和Hibernate Validator。
Bean Validation API是Java定义的一个验证参数的规范。
Hibernate Validator是Bean Validation API的一个实现。
@Valid和Validated的比较
Spring Validation验证框架对参数的验证机制提供了@Validated(Spring's JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),配合BindingResult可以直接提供参数验证结果。
@Valid : 没有分组功能,可以用在方法、构造函数、方法参数和成员属性(field)上,如果一个待验证的pojo类,其中还包含了待验证的对象,需要在待验证对象上注解@valid,才能验证待验证对象中的成员属性
@Validated :提供分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制,用在类型、方法和方法参数上。但不能用于成员属性(field)。
两者都可以用在方法入参上,但都无法单独提供嵌套验证功能,都能配合嵌套验证注解@Valid进行嵌套验证。
嵌套验证示例:
public class ClassRoom{ @NotNull String name; @Valid // 嵌套校验,校验参数内部的属性 @NotNull Student student; }
@GetMapping("/room") // 此处可使用 @Valid 或 @Validated, 将会进行嵌套校验 public String validator(@Validated ClassRoom classRoom, BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
BindingResult 的使用
BindingResult必须跟在被校验参数之后,若被校验参数之后没有BindingResult对象,将会抛出BindException。
@GetMapping("/room") public String validator(@Validated ClassRoom classRoom, BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
不要使用 BindingResult 接收String等简单对象的错误信息。简单对象校验失败,会抛出 ConstraintViolationException。主要就是接不着,你要写也算是没关系…
// ❌ 错误用法,也没有特别的错,只是 result 是接不到值。 @GetMapping("/room") @Validated // 启用校验 public String validator(@NotNull String name, BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
修改校验失败的提示信息
可以通过各个校验注解的message属性设置更友好的提示信息。
public class ClassRoom{ @NotNull(message = "Classroom name must not be null") String name; @Valid @NotNull Student student; }
@GetMapping("/room") @Validated public String validator(ClassRoom classRoom, BindingResult result, @NotNull(message = "姓名不能为空") String name) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
message属性配置国际化的消息也可以的,message中填写国际化消息的code,在抛出异常时根据code处理一下就好了。
@GetMapping("/room") @Validated public String validator(@NotNull(message = "demo.message.notnull") String name) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
// message_zh_CN.properties demo.message.notnull=xxx消息不能为空 // message_en_US.properties demo.message.notnull=xxx message must no be null
hibernate-validator 的使用
1.引入pom
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.3.1.Final</version> </dependency>
2.dto入参对象属性加入注解
@Data public class UserModel implements Serializable { private String id; @NotBlank(message = "用户名不能为空") private String name; @NotNull(message = "性别不能不填") private Byte gender; @NotNull(message = "年龄不能不填") @Min(value = 0,message = "年龄必须大于0岁") @Max(value = 150,message = "年龄必须小于150岁") private Integer age; @NotBlank(message = "手机号不能不填") private String telphone; private String registerMode; private String thirdPartyId; private String encrptPassward; }
方法一:3.controller方法入参加入校验(@Validated )
@GetMapping("/getUser") public String validator(@Validated UserModel userModel , BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "ok"; }
方法二:3.自定义封装ValidatorImpl类
@Component public class ValidatorImpl implements InitializingBean{ private Validator validator; //实现校验方法并返回校验结果 public ValidationResult validate(Object bean){ final ValidationResult result=new ValidationResult(); Set<ConstraintViolation<Object>> validate = validator.validate(bean); if (validate.size()>0) { result.setHasError(true); validate.forEach(constraintViolation->{ String errMsg=constraintViolation.getMessage(); String propertyName=constraintViolation.getPropertyPath().toString(); result.getErrorMsgMap().put(propertyName,errMsg); }); } return result; } @Override public void afterPropertiesSet() throws Exception { this.validator= Validation.buildDefaultValidatorFactory().getValidator(); } }
方法二:4.自定义封装ValidationResult 类
public class ValidationResult { public boolean hasError=false; private Map<String,String> errorMsgMap=new HashMap<>(); //实现通用的通过格式化字符串信息获取错误结果的msg方法 public String getErrMsg(){ return StringUtils.join(errorMsgMap.values().toArray(),","); } public boolean isHasError() { return hasError; } public void setHasError(boolean hasError) { this.hasError = hasError; } public Map<String, String> getErrorMsgMap() { return errorMsgMap; } public void setErrorMsgMap(Map<String, String> errorMsgMap) { this.errorMsgMap = errorMsgMap; } }
5.controller方法入参加入校验
@Autowired private ValidatorImpl validator; @Override @Transactional(rollbackFor = Exception.class) public void register(UserModel userModel) throws BusinessException { UserDo userDo=new UserDo(); if (userModel == null) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR); } //validate进行入参校验 ValidationResult validate = validator.validate(userModel); if (validate.isHasError()){ throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,validate.getErrMsg()); } }
Bean Validation 的约束
@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(value)
被注释的元素必须符合指定的正则表达式
Hibernate Validator 附加的约束
Hibernate Validator 附加的 constraint:
@Email
被注释的元素必须是电子邮箱地址@Length
被注释的字符串的大小必须在指定的范围内@NotEmpty
被注释的字符串的必须非空@Range
被注释的元素必须在合适的范围内
以上为个人经验,希望能给大家一个参考,也希望大家多多支持猪先飞。
相关文章
- 这篇文章主要给大家介绍了关于Nest.js参数校验和自定义返回数据格式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-28
校验非空的注解@NotNull如何取得自定义的message
这篇文章主要介绍了校验非空的注解@NotNull如何取得自定义的message,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-29- 这篇文章主要介绍了解决Springboot get请求是参数过长的情况,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-17
PHP中empty和isset对于参数结构的判断及empty()和isset()的区别
废话不多说了,直接给大家贴代码了。<?php class test{} $a1 = null; $a2 = ""; //$a3 = $a4 = 0; $a5 = '0'; $a6 = false; $a7 = array(); //var $a8; $a9 = new test(); for ($i=1; $i <=9 ; $i++) {...2015-11-24- 这篇文章主要介绍了java正则表达式判断前端参数修改表中另一个字段的值,需要的朋友可以参考下...2021-05-07
- mysql安装成功后有几个默认的配置模板,列表如下: my-huge.cnf : 用于高端产品服务器,包括1到2GB RAM,主要运行mysql my-innodb-heavy-4G.ini : 用于只有innodb的安装,最多有4GB RAM,支持大的查询和低流量 my-large.cnf : 用于...2015-03-15
- 这篇文章主要介绍了C#泛型的类型参数约束的相关资料,文中讲解非常细致,帮助大家更好的理解和学习c#,感兴趣的朋友可以了解下...2020-07-31
- 这篇文章主要给大家介绍了关于C#中out参数、ref参数与值参数的用法及区别的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
- 这篇文章主要介绍了Vue 组件复用多次自定义参数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-27
- 这篇文章主要介绍了Java线程池中的各个参数如何合理设置操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-19
- 这篇文章主要介绍了详解Java后端优雅验证参数合法性,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-18
处理@PathVariable注解允许参数为空、允许不传参数的问题
这篇文章主要介绍了处理@PathVariable注解允许参数为空、允许不传参数的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-23- 这篇文章主要介绍了vue+axios全局添加请求头和参数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-24
- 这篇文章主要介绍了SpringBoot接口接收json参数解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-19
- 这篇文章主要介绍了pytorch 实现冻结部分参数训练另一部分,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-27
详解element-ui 表单校验 Rules 配置 常用黑科技
这篇文章主要介绍了element-ui 表单校验 Rules 配置 常用黑科技,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-07-11- 这篇文章主要介绍了C#向线程中传递多个参数的解决方法(两种)的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下...2020-06-25
- 这篇文章主要介绍了spring cloud gateway中如何读取请求参数的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-07-15
- 这篇文章主要介绍了Springboot实现多线程注入bean的工具类操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-27
- 这篇文章主要介绍了使用JavaScript获取URL中的参数(两种方法)的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下...2016-11-22