软件编程
位置:首页>> 软件编程>> java编程>> Spring Boot利用JSR303实现参数验证的方法实例

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

作者:程序员果果  发布时间:2022-07-28 20:36:26 

标签:springboot,验证,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 规范内嵌的约束注解

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

实例

基本应用

引入依赖


<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();
}

}

测试如下:

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

异常的统一处理

参数校验不通过时,会抛出 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();
}

}

测试如下:

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

自定义校验注解

虽然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;

}

测试如下:

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

源码地址

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

来源:https://mp.weixin.qq.com/s/Wq1pHIExP6WKUFMV5EJ_sg

0
投稿

猜你喜欢

  • 经典的排序算法有八种,分别为:冒泡排序选择排序插入排序归并排序希尔排序快速排序堆排序基数排序其中冒泡排序、选择排序、插入排序称为三大基本排序
  • 前言最近项目中又一次需要集成友盟的三方登录与分享,之前没有记录过,所以这次来写一下...准备工作1.注册友盟账号创建应用,获取key:申请地
  • 一、百度百科Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防
  • 简单说明一下:线程池可以看做容纳线程的容器;一个应用程序最多只能有一个线程池;ThreadPool静态类通过QueueUserWorkIte
  • 目录1.Groovy特性2.核心涉及3.Java与Groovy转换第一步:引入Groovy依赖第二步:创建interface接口声明方法第三
  • 本文实例为大家分享了Unity实现打砖块游戏的具体代码,供大家参考,具体内容如下效果演示1.创建墙1.1我们用预制体来统一管理墙方便以后对墙
  • Java设计模式的模板方法模式定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法中的某些特定步
  • 为此Android在appcompat-v7库中提供了一个NotificationCompat类来处理新老版本的兼容问题,我们在编写通知功能
  • Struts2Struts2是在WebWork2基础发展而来的。和struts1一样, Struts2也属于MVC框架。不过有一点
  • 前言从 Java 5.0 开始,String 类新增了一个强大的字符串格式化方法 format()。这个方法到现在用的人还是不多,实在是一种
  • 本文实例为大家分享了本地图片或者网络图片高斯模糊效果(毛玻璃效果),具体内容如下首先看效果图1.本地图片高斯模糊2.网络图片高斯模糊gith
  • 本文实例为大家分享了java中文传值乱码问题,以及解决方法,供大家参考,具体内容如下一般编码格式设置:1.可以经过两次编码处理,即设置字符集
  • 单元测试单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,因此,单元测试就是针对Java方法的测试,进而检查方法
  • 这周在做公司的一个C#项目中,要写一个webservice提供一个下载方法,之前公司有过,但是要整改,于是这种鸟屎摊子又交给了我,其中一个密
  • 1. 你知道线程安全问题吗?线程安全问题:一般指在多线程模式下,多个线程对同一个共享数据进行操作时,第一个线程还没来得及更新共享数据,从而导
  • DSLDomain-specific language: 一种专注于某一领域,仅针对部分表达方式的计算机编程语言。特点方法链 Method
  • merge结合include优化android布局,效果不知道,个人感觉使用上也有很大的局限,不过还是了解一下,记录下来。布局文件都要有根节
  • 一次正常的请求最近别人需要调用我们系统的某一个功能,对方希望提供一个api让其能够更新数据。由于该同学是客户端开发,于是有了类似以下代码。@
  • 由于之前一直在使用spring-boot开发,但一直苦恼于不知道如何利用debug调试代码.在网上查找了很多文章之后。学习之余决定把spri
  • 为什么要使用路由在之前我们的代码中,页面跳转使用的代码如下所示:Navigator.of(context).push(  Mate
手机版 软件编程 asp之家 www.aspxhome.com