Spring Boot利用JSR303实现参数验证的方法实例

简介

JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation。

在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情。应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的。在通常的情况下,应用程序是分层的,不同的层由不同的开发人员来完成。很多时候同样的数据验证逻辑会出现在不同的层,这样就会导致代码冗余和一些管理的问题,比如说语义的一致性等。为了避免这样的情况发生,最好是将验证逻辑与相应的域模型进行绑定。

Bean Validation 为 JavaBean 验证定义了相应的元数据模型和 API。缺省的元数据是 Java Annotations,通过使用 XML 可以对原有的元数据信息进行覆盖和扩展。在应用程序中,通过使用 Bean Validation 或是你自己定义的 constraint,例如 @NotNull, @Max, @ZipCode, 就可以确保数据模型(JavaBean)的正确性。constraint 可以附加到字段,getter 方法,类或者接口上面。对于一些特定的需求,用户可以很容易的开发定制化的 constraint。Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。

Bean Validation 规范内嵌的约束注解

实例

基本应用

引入依赖

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

给参数对象添加校验注解

@Data
public class User {

 private Integer id;
 @NotBlank(message = "用户名不能为空")
 private String username;
 @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密码必须为8~16个字母和数字组合")
 private String password;
 @Email
 private String email;
 private Integer gender;

}

Controller 中需要校验的参数Bean前添加 @Valid 开启校验功能,紧跟在校验的Bean后添加一个BindingResult,BindingResult封装了前面Bean的校验结果。

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

 @PostMapping("")
 public Result save (@Valid User user , BindingResult bindingResult) {
  if (bindingResult.hasErrors()) {
   Map<String , String> map = new HashMap<>();
   bindingResult.getFieldErrors().forEach( (item) -> {
    String message = item.getDefaultMessage();
    String field = item.getField();
    map.put( field , message );
   } );
   return Result.build( 400 , "非法参数 !" , map);
  }
  return Result.ok();
 }

}

测试如下:

异常的统一处理

参数校验不通过时,会抛出 BingBindException 异常,可以在统一异常处理中,做统一处理,这样就不用在每个需要参数校验的地方都用 BindingResult 获取校验结果了。

@Slf4j
@RestControllerAdvice(basePackages = "com.itwolfed.controller")
public class GlobalExceptionControllerAdvice {

 @ExceptionHandler(value= {MethodArgumentNotValidException.class , BindException.class})
 public Result handleVaildException(Exception e){
  BindingResult bindingResult = null;
  if (e instanceof MethodArgumentNotValidException) {
   bindingResult = ((MethodArgumentNotValidException)e).getBindingResult();
  } else if (e instanceof BindException) {
   bindingResult = ((BindException)e).getBindingResult();
  }
  Map<String,String> errorMap = new HashMap<>(16);
  bindingResult.getFieldErrors().forEach((fieldError)->
    errorMap.put(fieldError.getField(),fieldError.getDefaultMessage())
  );
  return Result.build(400 , "非法参数 !" , errorMap);
 }

}

分组解决校验

新增和修改对于实体的校验规则是不同的,例如id是自增的时候,新增时id要为空,修改则必须不为空;新增和修改,若用的恰好又是同一种实体,那就需要用到分组校验。

校验注解都有一个groups属性,可以将校验注解分组,我们看下@NotNull的源码:

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {

 String message() default "{javax.validation.constraints.NotNull.message}";

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

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

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

  NotNull[] value();
 }
}

从源码可以看出 groups 是一个Class<?>类型的数组,那么就可以创建一个Groups.

public class Groups {
 public interface Add{}
 public interface Update{}
}

给参数对象的校验注解添加分组

@Data
public class User {

 @Null(message = "新增不需要指定id" , groups = Groups.Add.class)
 @NotNull(message = "修改需要指定id" , groups = Groups.Update.class)
 private Integer id;
 @NotBlank(message = "用户名不能为空")
 @NotNull
 private String username;
 @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密码必须为8~16个字母和数字组合")
 private String password;
 @Email
 private String email;
 private Integer gender;

}

Controller 中原先的@Valid不能指定分组 ,需要替换成@Validated

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

 @PostMapping("")
 public Result save (@Validated(Groups.Add.class) User user) {
  return Result.ok();
 }

}

测试如下:

自定义校验注解

虽然JSR303和springboot-validator 已经提供了很多校验注解,但是当面对复杂参数校验时,还是不能满足我们的要求,这时候我们就需要 自定义校验注解。

例如User中的gender,用 1代表男 2代表女,我们自定义一个校验注解@ListValue,指定取值只能1和2。

创建约束规则

@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface ListValue {
 String message() default "";

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

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

 int[] vals() default { };
}

一个标注(annotation) 是通过@interface关键字来定义的. 这个标注中的属性是声明成类似方法 的样式的. 根据Bean Validation API 规范的要求:

  • message属性, 这个属性被用来定义默认得消息模版, 当这个约束条件被验证失败的时候,通过 此属性来输出错误信息。
  • groups 属性, 用于指定这个约束条件属于哪(些)个校验组. 这个的默认值必须是Class<?>类型数组。
  • payload 属性, Bean Validation API 的使用者可以通过此属性来给约束条件指定严重级别. 这个属性并不被API自身所使用。

除了这三个强制性要求的属性(message, groups 和 payload) 之外, 我们还添 加了一个属性用来指定所要求的值. 此属性的名称vals在annotation的定义中比较特 殊, 如果只有这个属性被赋值了的话, 那么, 在使用此annotation到时候可以忽略此属性名称.

另外, 我们还给这个annotation标注了一些元标注( meta annotatioins):

  • @Target({ METHOD, FIELD, ANNOTATION_TYPE }): 表示此注解可以被用在方法, 字段或者 annotation声明上。
  • @Retention(RUNTIME): 表示这个标注信息是在运行期通过反射被读取的.
  • @Constraint(validatedBy = ListValueConstraintValidator.class): 指明使用哪个校验器(类) 去校验使用了此标注的元素.
  • @Documented: 表示在对使用了该注解的类进行javadoc操作到时候, 这个标注会被添加到 javadoc当中.

创建约束校验器

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

 private Set<Integer> set = new HashSet<>();
 /**
  * 初始化方法
  */
 @Override
 public void initialize(ListValue constraintAnnotation) {

  int[] vals = constraintAnnotation.vals();
  for (int val : vals) {
   set.add(val);
  }

 }

 /**
  * 判断是否校验成功
  *
  * @param value 需要校验的值
  * @param context
  * @return
  */
 @Override
 public boolean isValid(Integer value, ConstraintValidatorContext context) {

  return set.contains(value);
 }
}

ListValueConstraintValidator定义了两个泛型参数, 第一个是这个校验器所服务到标注类型(在我们的例子中即ListValue), 第二个这个校验器所支持到被校验元素的类型 (即Integer)。

如果一个约束标注支持多种类型的被校验元素的话, 那么需要为每个所支持的类型定义一个ConstraintValidator,并且注册到约束标注中。

这个验证器的实现就很平常了, initialize() 方法传进来一个所要验证的标注类型的实例, 在本 例中, 我们通过此实例来获取其vals属性的值,并将其保存为Set集合中供下一步使 用。

isValid()是实现真正的校验逻辑的地方, 判断一个给定的int对于@ListValue这个约束条件来说 是否是合法的。

在参数对象中使用@ListValue注解。

@Data
public class User {

 @Null(message = "新增不需要指定id" , groups = Groups.Add.class)
 @NotNull(message = "修改需要指定id" , groups = Groups.Update.class)
 private Integer id;
 @NotBlank(message = "用户名不能为空")
 @NotNull
 private String username;
 @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密码必须为8~16个字母和数字组合")
 private String password;
 @Email
 private String email;
 @ListValue( message = "性别应指定相应的值" , vals = {1,2} , groups = {Groups.Add.class , Groups.Update.class})
 private Integer gender;

}

测试如下:

源码地址

https://github.com/gf-huanchupk/SpringBootLearning

参考

  • https://www.ibm.com/developerworks/cn/java/j-lo-jsr303/index.html
  • https://docs.jboss.org/hibernate/validator/4.3/reference/zh-CN/pdf/hibernate validator reference.pdf

总结

到此这篇关于Spring Boot利用JSR303实现参数验证的文章就介绍到这了,更多相关Spring Boot用JSR303参数验证内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(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,

  • Kotlin + Spring Boot 请求参数验证的代码实例

    编写 Web 应用程序的时候,经常要做的事就是要对前端传回的数据进行简单的验证,比如是否非空.字符长度是否满足要求,邮箱格式是否正确等等.在 Spring Boot 中,可以使用 Bean Validation (JSR-303) 技术通过注解的方式来进行参数验证. 准备 DTO 对象 data class UserRegisterModel( @get: NotEmpty(message = "User name is required") @get: Size(message =

  • Spring Boot利用JSR303实现参数验证的方法实例

    简介 JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation. 在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情.应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的.在通常的情况下,应用程序是分层的,不同的层由不同的开发人员来完成.很多时候同样的数据验证逻辑会出现在不同的层,这样就会导致代码冗余和一些管理的问题,比如说语义的一致性等.为了避免这样的情况发生,最好是将验证逻辑与相应的域模型进行绑定. Bean Va

  • .Net Web Api中利用FluentValidate进行参数验证的方法

    前言 本文主要介绍了关于.Net Web Api用FluentValidate参数验证的相关内容,下面话不多说了,来一起看看详细的介绍吧. 方法如下 安装FluentValidate 在ASP.NET Web Api中请安装 FluentValidation.WebApi版本 创建一个需要验证的Model public class Product { public string name { get; set; } public string des { get; set; } public s

  • Spring Boot中使用Redis做缓存的方法实例

    前言 本文主要给大家介绍的是关于Spring Boot中使用Redis做缓存的相关内容,这里有两种方式: 使用注解方式(但是小爷不喜欢) 直接<Spring Boot 使用 Redis>中的redisTemplate 下面来看看详细的介绍: 1.创建UserService public interface UserService { public User findById(int id); public User create(User user); public User update(U

  • Spring boot打包jar分离lib和resources方法实例

    为什么要配置.依赖文件分离: 1.在传统jar包中,所有文件都打包到一个jar包中,jar非常臃肿,在进行服务更新部署时非常不便,而且传输不稳定时导致传输失败.如果过实行文件分离.在依赖不改变的情况下,仅仅上传更新后的 编译文件是非常方便的. 如果要修改一些配置文件:properties.xml,静态文件等可以直接在服务器上编辑. 那么怎么实行配置.依赖文件分离呢? 插件介绍 maven-jar-plugin 这个插件式专门用来打包用的,可以配置需要打包进去的文件,程序的入口类等. maven-

  • Spring Boot中自动执行sql脚本的方法实例

    目录 背景 实现核心 实现方法 注意 总结 说明:所有的代码基于SpringBoot 2.0.3版本 背景 在应用程序启动后,可以自动执行建库.建表等SQL脚本.下文中以要自动化执行people.sql脚本为例说明,脚本在SpringBoot工程中的路径为:classpath:people.sql,脚本的具体内容如下: CREATE TABLE IF NOT EXISTS people( persion_id BIGINT NOT NULL AUTO_INCREMENT, first_name

  • spring boot 利用注解实现权限验证的实现代码

    这里使用 aop 来实现权限验证 引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 定义注解 package com.lmxdawn.api.admin.annotation; import java.lang.annotation.El

  • Spring Boot利用@Async如何实现异步调用:自定义线程池

    前言 在之前的Spring Boot基础教程系列中,已经通过<Spring Boot中使用@Async实现异步调用>一文介绍过如何使用@Async注解来实现异步调用了.但是,对于这些异步执行的控制是我们保障自身应用健康的基本技能.本文我们就来学习一下,如果通过自定义线程池的方式来控制异步调用的并发. 本文中的例子我们可以在之前的例子基础上修改,也可以创建一个全新的Spring Boot项目来尝试. 定义线程池 第一步,先在Spring Boot主类中定义一个线程池,比如: @SpringBoo

  • Spring boot进行参数校验的方法实例详解

    Spring boot开发web项目有时候我们需要对controller层传过来的参数进行一些基本的校验,比如非空.整数值的范围.字符串的长度.日期.邮箱等等.Spring支持JSR-303 Bean Validation API,可以方便的进行校验. 使用注解进行校验 先定义一个form的封装对象 class RequestForm { @Size(min = 1, max = 5) private String name; public String getName() { return n

  • Spring Boot 利用WebUploader进行文件上传功能

    Web Uploader简介 WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件.在现代的浏览器里面能充分发挥HTML5的优势,同时又不摒弃主流IE浏览器,沿用原来的FLASH运行时,兼容IE6+,iOS 6+, android 4+.两套运行时,同样的调用方式,可供用户任意选用.采用大文件分片并发上传,极大的提高了文件上传效率. 我们这里使用官网的一个例子来实现我们个人头像的上传. 我们的重点是在Spring Boo

  • spring boot中的properties参数配置详解

    application.properties application.properties是spring boot默认的配置文件,spring boot默认会在以下两个路径搜索并加载这个文件 src\main\resources src\main\resources\config 配置系统参数 在application.properties中可配置一些系统参数,spring boot会自动加载这个参数到相应的功能,如下 #端口,默认为8080 server.port=80 #访问路径,默认为/

随机推荐