springboot使用hibernate validation对参数校验的实现方法

springboot天生支持使用hibernate validation对参数的优雅校验,如果不使用它,只能对参数挨个进行如下方式的手工校验,不仅难看,使用起来还很不方便:

if(StringUtils.isEmpty(userName)){
	throw new RuntimeException("用户名不能为空");
}

下面将介绍hibernate validation的基本使用方法。

一、引入依赖

这里在springboot 2.4.1中进行实验,引入以下依赖:

<parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.4.1</version>
 <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 </dependency>
 <dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <version>1.18.16</version>
 </dependency>
 <dependency>
 <groupId>org.hibernate.validator</groupId>
 <artifactId>hibernate-validator</artifactId>
 <version>6.1.6.Final</version>
 </dependency>
</dependencies>

二、基本请求参数校验

如下的一个spring mvc的请求调用中有一个id参数(Integer类型),如果不允许它为空,该怎么做

  • 在Controller上加上@Validated注解
  • 在需要校验的字段前面加上@NotNull(message = "用户id不能为空")注解
  • 定义全局异常处理类,定制化返回结果
 @RestControllerAdvice
 @Slf4j
 public class ValidationAdvice {

 @ExceptionHandler(Exception.class)
 @ResponseBody
 public WrapperResult handler(Exception e) {
  //获取异常信息,获取异常堆栈的完整异常信息
  StringWriter sw = new StringWriter();
  PrintWriter pw = new PrintWriter(sw);
  e.printStackTrace(pw);
  //日志输出异常详情
  log.error(sw.toString());
  return WrapperResult.faild("服务异常,请稍后再试");
 }

 @ExceptionHandler(ConstraintViolationException.class)
 @ResponseBody
 public WrapperResult handler(ConstraintViolationException e) {
  StringBuffer errorMsg = new StringBuffer();
  Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
  violations.forEach(x -> errorMsg.append(x.getMessage()).append(";"));
  return WrapperResult.faild(errorMsg.toString());
 }
 }

Controller层代码如下所示:

 @RestController
 @Slf4j
 @RequestMapping("/user")
 @Validated
 public class UserController {

 /**
 * 根据id查询用户信息
 *
 * @param id
 * @return
 */
 @GetMapping
 public WrapperResult<UserModel> findUser(@NotNull(message = "用户id不能为空")
      @RequestParam(value = "id")
      String id) {
  return WrapperResult.success(new UserModel());
 }
 }

如果发起请求127.0.0.1:8080/user?id= 则会返回结果

{
 "status": 1,
 "data": "用户id不能为空;",
 "msg": "FAIL",
 "success": false
 }

三、对象内参数校验

上面是GET请求,下面介绍POST请求,请求对象内的参数校验。

1.Controller类上加上@Validated注解

@RestController
@Slf4j
@RequestMapping("/user")
**@Validated**
public class UserController {
}

2.在POST请求方法参数前面加上@Validated 注解

 @PostMapping("/mobile-regist")
 public WrapperResult<Boolean> mobileRegit(@Validated @RequestBody UserModel userModel) {
 return WrapperResult.success(true);
 }

3.在上面介绍的ValidationAdvice类中加上对象参数校验异常捕获

//处理校验异常,对于对象类型的数据的校验异常
 @ExceptionHandler(MethodArgumentNotValidException.class)
 @ResponseBody
 public WrapperResult handler(MethodArgumentNotValidException e) {
 StringBuffer sb = new StringBuffer();
 List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
 allErrors.forEach(msg -> sb.append(msg.getDefaultMessage()).append(";"));
 return WrapperResult.faild(sb.toString());
 }

UserModel类的定义如下:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class UserModel {

 @NotEmpty(message = "姓名不能为空")
 private String name;

 @NotEmpty(message = "手机号不能为空")
// @Mobile(message = "手机号格式不正确")
 private String mobile;

 @NotEmpty(message = "电子邮箱不能为空")
	@Email(message = "电子邮箱格式不正确")
 private String email;

 private String password;

 private String address;

 @NotNull(message = "年龄不能为空")
 @Min(value = 12, message = "允许注册年龄最小为12岁")
 @Max(value = 24, message = "允许年龄最大为24岁")
 private Integer age;

 @NotEmpty(message = "联系人不允许为空")
 @Size(min = 1, max = 3, message = "联系人长度只允许1到3之间")
 private List<String> contacts;
}

如果POST请求如下所示

{
 "name":"",
 "mobile":"12666666666",
 "email":"",
 "password":"",
 "address":"",
 "age": null,
 "contacts":[

 ]
}

则会返回如下定制化返回结果:

{
 "status": 1,
 "data": "电子邮箱不能为空;联系人长度只允许1到3之间;年龄不能为空;联系人不允许为空;姓名不能为空;手机号格式不正确;",
 "msg": "FAIL",
 "success": false
}

四、自定义校验器

像是@NotNull、@Email等注解都是hibernate validation 内置的注解,我们想开发像是@Email注解一样功能的注解,如何做呢,比如@Mobile,它的使用方法将和@Email一模一样。

首先,先定义一个工具类存放ValidationUtil两个常量值

public class ValidationUtil {
 //手机号校验正则
 public static final String MOBILE_REGX = "^[1][3-9][0-9]{9}$";

 public static final String MOBILE_MSG = "手机号格式错误";
}

1.定义注解Mobile

具体代码可以参考@Email的实现,直接将Email名字改成Mobile即可,如下所示:

@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface Mobile {

 String message() default ValidationUtil.MOBILE_MSG;

 Class<?>[] groups() default {};

 Class<? extends Payload>[] payload() default {};

 String regexp() default ValidationUtil.MOBILE_REGX;

 Pattern.Flag[] flags() default {};

 @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
 @Retention(RUNTIME)
 @Documented
 public @interface List {
 Mobile[] value();
 }
}

2.定义MobileValidator实现对参数的校验逻辑

public class MobileValidator implements ConstraintValidator<Mobile, String> {

 private String regexp;

 @Override
 public void initialize(Mobile constraintAnnotation) {
 //获取校验的手机号的格式
 this.regexp = constraintAnnotation.regexp();
 }

 @Override
 public boolean isValid(String value, ConstraintValidatorContext context) {
 if (!StringUtils.hasText(value)) {
  return true;
 }
 return value.matches(regexp);
 }
}

3.使用方法和@Email一模一样

不赘述

五、分组校验

假设一个用户注册的场景,用户注册有三种方式

  • 用户名+图形验证码注册
  • 邮箱+邮箱验证码注册
  • 手机号+短信验证码注册

用户注册的时候除了方式不一样,其他用户信息基本相同,后端开了三个接口对应着着三种注册方式,请求体中我们使用一个Model封装了以上所有信息,包含着用户名、邮箱、手机号等信息,这时候不同的接口被调用,model中需要校验的参数就不一样了:

用户名注册的时候邮箱地址和手机号可以为空,但是用户名不能为空;通过邮箱注册的时候,邮箱地址不能为空,但是用户名和手机号可以为空;......

分组校验专门应对这种情况。

1.首先定义三个接口,表示三种组类别

public interface ValidEmail {
}

public interface ValidMobile {
}

public interface ValidUserName {
}

2.在UserModel实体类上指名组类别

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class UserModel {

 @NotEmpty(message = "姓名不能为空", groups = {ValidUserName.class})
 @UserName(groups = {ValidUserName.class})
 private String name;

 @NotEmpty(message = "手机号不能为空", groups = {ValidMobile.class})
 @Mobile(groups = {ValidMobile.class})
 private String mobile;

 @NotEmpty(message = "电子邮箱不能为空", groups = {ValidEmail.class})
 @Email(message = "电子邮箱格式不正确", groups = {ValidEmail.class})
 private String email;

 private String password;

 private String address;

 @NotNull(message = "年龄不能为空")
 @Min(value = 12, message = "允许注册年龄最小为12岁", groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
 @Max(value = 24, message = "允许年龄最大为24岁",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
 private Integer age;

 @NotEmpty(message = "联系人不允许为空",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
 @Size(min = 1, max = 3, message = "联系人长度只允许1到3之间",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
 private List<String> contacts;
}

3.Controller方法上指名验证组别

 /**
 * 手机号注册
 *
 * @param userModel
 * @return
 */
 @PostMapping("/mobile-regist")
 public WrapperResult<Boolean> mobileRegit(@Validated(ValidMobile.class) @RequestBody UserModel userModel) {
 return WrapperResult.success(true);
 }

这时候进行如下请求:

POST http://127.0.0.1:8080/user/mobile-regist

{
 "mobile":"12666666666",
 "password":"",
 "address":"",
 "age": null,
 "contacts":[

 ]

}

则会返回结果:

{
"status": 1,
"data": "联系人长度只允许1到3之间;手机号格式错误;联系人不允许为空;",
"msg": "FAIL",
"success": false
}

该请求中并没有传递email和username字段,而且结果中也未校验出这两个字段,符合预期结果。

六、手动校验

此处的手动校验并非是使用if/else进行简单的手动校验,而是使用Validation自带的校验工具对使用了@NotNull等注解的实体对象进行属性校验。

首先先获取Valiation对象:

private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

1. 全属性校验

/**
 * 验证某个对象所有字段
 *
 * @param obj
 * @param <T>
 * @return
 */
public static <T> ValidationResult validateEntity(T obj) {
 ValidationResult result = new ValidationResult();
 Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class);
 if (!CollectionUtils.isEmpty(set)) {
 result.setHasErrors(true);
 Map<String, String> errorMsg = new HashMap<>();
 for (ConstraintViolation<T> cv : set) {
  errorMsg.put(cv.getPropertyPath().toString(), cv.getMessage());
 }
 result.setErrorMsg(errorMsg);
 }
 return result;
}

2.某个字段的单独校验

/**
* 验证某个对象某个字段
*
* @param obj
* @param propertyName
* @param <T>
* @return
*/
public static <T> ValidationResult validateProperty(T obj, String propertyName) {
 ValidationResult result = new ValidationResult();
 Set<ConstraintViolation<T>> set = validator.validateProperty(obj, propertyName, Default.class);
 if (!CollectionUtils.isEmpty(set)) {
 result.setHasErrors(true);
 Map<String, String> errorMsg = new HashMap<>();
 for (ConstraintViolation<T> cv : set) {
  errorMsg.put(propertyName, cv.getMessage());
 }
 result.setErrorMsg(errorMsg);
 }
 return result;
}

ValidationResult的定义如下:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class ValidationResult {
 private Boolean hasErrors;
 private Map<String, String> errorMsg;
}

七、文件上传校验

1.tomcat容器下文件上传校验

在springboot+tomcat架构下的文件上传校验,假如已经有了如下的配置:

spring:
 servlet:
 multipart:
 max-file-size: 1MB
 max-request-size: 1MB

这表示只允许上传小于1MB大小的文件,如果不指定异常处理器,默认会报前端400,在ValidationAdvice类中添加如下代码可以自定义返回结果:

//文件上传文件大小超出限制
 @ExceptionHandler(MaxUploadSizeExceededException.class)
 @ResponseBody
 public WrapperResult<Map<String,Object>> fileSizeException(MaxUploadSizeExceededException exception) {
 log.error("文件太大,上传失败",exception);
 return WrapperResult.faild("只允许上传不大于"+exception.getMaxUploadSize()+"的文件");
 }

2.其它容器

在Jetty容器中1中的方法可能会失效,未验证;在undertow容器中是一定会失效,已经验证。undertow容器毕竟和spring-boot没有完全打磨好,不建议现阶段使用。

八、附录

1.所有校验规则注解说明

注解 说明
@Null 被注解的元素必须为空
@NotNull 被注解的元素必须不为空
@AssertTrue 被注解的元素必须为true
@AssertFlase 被注解的元素必须为false
@Min(value) 被注解的元素必须是数字,且必须大于指定的最小值
@Max(value) 被注解的元素必须是数字,且必须小于指定的最大值
@DecimalMin(value) 被注解的元素必须是数字,且必须大于指定的最小值
@DecaimalMax(value) 被注解的元素必须是数字,且必须小于指定的最大值
@Size(max=,min=) 被注解元素的大小必须在指定的范围内
@Digit(integer,fraction) 被注解元素必须是数字,且其值必须在可接受的范围内
@Past 被注解元素必须是一个过去的日期
@Futrue 被注解元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注解元素必须符合指定的正则表达式
@NotBlank 验证非空,且长度必须大于0
@Email 被注解的元素必须是电子邮件地址
@Length(max=,min=) 被注解的字符串大小必须在指定的范围内
@NotEmpty 被注解的字符串必须非空
@Range(max=,min=) 被注解的元素必须在指定范围内

2.校验规则注解例子

// 空和非空检查: @Null、@NotNull、@NotBlank、@NotEmpty
@Null(message = "验证是否为 null")
private Integer isNull;

@NotNull(message = "验证是否不为 null, 但无法查检长度为0的空字符串")
private Integer id;

@NotBlank(message = "检查字符串是不是为 null,以及去除空格后长度是否大于0")
private String name;

@NotEmpty(message = "检查是否为 NULL 或者是 EMPTY")
private List<String> stringList;

// Boolean值检查: @AssertTrue、@AssertFalse
@AssertTrue(message = " 验证 Boolean参数是否为 true")
private Boolean isTrue;

@AssertFalse(message = "验证 Boolean 参数是否为 false ")
private Boolean isFalse;

// 长度检查: @Size、@Length
@Size(min = 1, max = 2, message = "验证(Array,Collection,Map,String)长度是否在给定范围内")
private List<Integer> integerList;

@Length(min = 8, max = 30, message = "验证字符串长度是否在给定范围内")
private String address;

// 日期检查: @Future、@FutureOrPresent、@Past、@PastOrPresent
@Future(message = "验证日期是否在当前时间之后")
private Date futureDate;

@FutureOrPresent(message = "验证日期是否为当前时间或之后")
private Date futureOrPresentDate;

@Past(message = "验证日期是否在当前时间之前")
private Date pastDate;

@PastOrPresent(message = "验证日期是否为当前时间或之前")
private Date pastOrPresentDate;

// 其它检查: @Email、@CreditCardNumber、@URL、@Pattern、
@ScriptAssert、@UniqueElements
@Email(message = "校验是否为正确的邮箱格式")
private String email;

@CreditCardNumber(message = "校验是否为正确的信用卡号")
private String creditCardNumber;

@URL(protocol = "http", host = "127.0.0.1", port = 8080, message= "校验是否为正确的URL地址")
private String url;

@Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "正则校验是否为正确的手机号")
private String phone;

// 对关联对象元素进行递归校验检查
@Valid
@UniqueElements(message = "校验集合中的元素是否唯一")
private List<CalendarEvent> calendarEvent;

@Data
@ScriptAssert(lang = "javascript", script ="_this.startDate.before(_this.endDate)",message = "通过脚本表达式校验参数")
private class CalendarEvent {
 private Date startDate;
 private Date endDate;
}

// 数值检查: @Min、@Max、@Range、@DecimalMin、@DecimalMax、@Digits
@Min(value = 0, message = "验证数值是否大于等于指定值")
@Max(value = 100, message = "验证数值是否小于等于指定值")
@Range(min = 0, max = 100, message = "验证数值是否在指定值区间范围内")
private Integer score;

@DecimalMin(value = "10.01", inclusive = false, message = "验证数值是否大于等于指定值")
@DecimalMax(value = "199.99", message = "验证数值是否小于等于指定值")
@Digits(integer = 3, fraction = 2, message = "限制整数位最多为3,小数位最多为2")
private BigDecimal money;

九、源代码地址

https://gitee.com/kdyzm/validation-spring-boot-demo

到此这篇关于spring-boot 使用hibernate validation对参数进行优雅的校验的文章就介绍到这了,更多相关spring-boot校验参数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Springboot集成JSR303参数校验的方法实现

    JSR303 是一套 JavaBean 参数校验的标准 1.pom导入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> 2.注解类型 (1)空检查 @Null 验证对象是否为null @NotNull 验证对象是否不为null,

  • 详解SpringBoot中的参数校验(项目实战)

    Java后端发工作中经常会对前端传递过来的参数做一些校验,在业务中还要抛出异常或者不断的返回异常时的校验信息,充满了if-else这种校验代码,在代码中相当冗长.例如说,用户注册时,会校验手机格式的正确性,用户名的长度等等.虽说前端也可以做参数校验,但是为了保证我们API接口的可靠性,以保证最终数据入库的正确性,后端进行参数校验不可忽视. Hibernate Validator 提供了一种统一方便的方式,让我们快速的实现参数校验. Hibernate Validator 使用注解,实现声明式校验

  • springboot+dubbo+validation 进行rpc参数校验的实现方法

    注意:本文dubbo 版本 2.8.4 springboot 版本 2.0.4.RELEASE 项目结构 test-rest (前端消费着,controller 层,springboot+maven项目) test-api (dubbo服务 的 api ,只记录 service 接口和 model ,maven 项目) test-provider(dubbo 服务提供者,实际的数据库操作及业务层, springboot+maven项目 ) 背景: 使用springmvc做restful,使用du

  • SpringBoot如何优雅的处理校验参数的方法

    前言 做web开发有一点很烦人就是要校验参数,基本上每个接口都要对参数进行校验,比如一些格式校验 非空校验都是必不可少的.如果参数比较少的话还是容易 处理的一但参数比较多了的话代码中就会出现大量的IF ELSE就比如下面这样: 这个例子只是校验了一下空参数.如果需要验证邮箱格式和手机号格式校验的话代码会更多,所以介绍一下validator通过注解的方式进行校验参数. 什么是Validator Bean Validation是Java定义的一套基于注解的数据校验规范,目前已经从JSR 303的1.

  • springboot使用hibernate validation对参数校验的实现方法

    springboot天生支持使用hibernate validation对参数的优雅校验,如果不使用它,只能对参数挨个进行如下方式的手工校验,不仅难看,使用起来还很不方便: if(StringUtils.isEmpty(userName)){ throw new RuntimeException("用户名不能为空"); } 下面将介绍hibernate validation的基本使用方法. 一.引入依赖 这里在springboot 2.4.1中进行实验,引入以下依赖: <pare

  • SpringBoot使用validation做参数校验的实现步骤

    1.添加依赖 直接添加 hibernate-validator <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.2.Final</version> </dependency> 添加spring-boot-starter-validat

  • SpringBoot使用validation做参数校验说明

    目录 1.添加依赖 直接添加 hibernate-validator 添加spring-boot-starter-validation 添加spring-boot-starter-web 2. 配置文件 3.统一异常处理 4.使用 1.添加依赖 直接添加 hibernate-validator <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-valid

  • SpringBoot + validation 接口参数校验的思路详解

    有参数传递的地方都少不了参数校验.在web开发中,前端的参数校验是为了用户体验,后端的参数校验是为了安全.试想一下,如果在controller层中没有经过任何校验的参数通过service层.dao层一路来到了数据库就可能导致严重的后果,最好的结果是查不出数据,严重一点就是报错,如果这些没有被校验的参数中包含了恶意代码,那就可能导致更严重的后果. 实践 一.引入依赖 <!--引入spring-boot-starter-validation--> <dependency> <gr

  • SpringBoot实现接口的各种参数校验的示例

    目录 1.添加依赖 2.接口参数校验 2.1 requestBody参数校验 2.2 requestParam/PathVariable参数校验 3.统一异常处理 4.进阶使用 4.1 分组校验 4.2 嵌套校验 4.3 集合校验 4.4 自定义校验 5.快速失败 (Fail Fast) 6.@Valid和@Validated区别 7.实现原理 7.1 requestBody参数校验实现原理 7.2 方法级别的参数校验实现原理 在我们进行接口开发时,在对参数的接收时,我们需要冗余复杂的校验规则

  • SpringBoot整合Hibernate Validator实现参数验证功能

    在前后端分离的开发模式中,后端对前端传入的参数的校验成了必不可少的一个环节.但是在多参数的情况下,在controller层加上参数验证,会显得特别臃肿,并且会有许多的重复代码.这里可以引用Hibernate Validator来解决这个问题,直接在实体类进行参数校验,验证失败直接返回错误信息给前端,减少controller层的代码量. 一.xml引入Hibernate Validator <!-- 验证器 --> <dependency> <groupId>org.hi

  • SpringBoot通过自定义注解实现参数校验

    目录 1. 为什么要进行参数校验 2. 如何实现参数校验 3. 注解实现参数校验 4. 自定义注解实现参数校验 1. 为什么要进行参数校验 在后端进行工作时,需要接收前端传来的数据去数据库查询,但是如果有些数据过于离谱,我们就可以直接把它pass掉,不让这种垃圾数据接触数据库,减小数据库的压力. 有时候会有不安分的人通过一些垃圾数据攻击咱们的程序,让咱们的服务器或数据库崩溃,这种攻击虽然低级但不得不防,就像QQ进行登录请求时,它们向后端发送 账号=123,密码=123 的数据,一秒钟还发1w次,

  • SpringBoot中的异常处理与参数校验的方法实现

    兄弟们好,这次来跟老铁交流两个问题,异常和参数校验,在说参数校验之前我们先来说异常处理吧,因为后面参数的校验会牵扯到异常处理这块的内容. 异常处理 说到异常处理,我不知道大家有没有写过或者遇到过如下的写法. public void saveUser() { try { // 所有的业务内容,目测几百行 }catch (Exception e) { e.printStackTrace(); } } 如果出现上述的代码,里面包含了大量的业务代码,如果是你写的,赶紧改掉,不是你写的找写的,吐槽赶紧改掉

  • Hibernate Validation自定义注解校验的实现

    情景:需要对String类型的属性比如description进行验证,验证规则是当description为空时不进行正则校验,description不为空时进行正则校验.上述需求Hibernate Validation没有可用于上述需求的注解,故自定义一个注解并自定义校验规则. 自定义注解进行校验的步骤 写一个校验注解,在注解中指定校验器类,校验注解与校验器一般一一对应. 写一个校验器类并在校验器类中写校验逻辑,校验器必须实现ConstraintValidator<?, ?>接口,第一个参数是

随机推荐