概述

在 Web 应用中,客户端提交数据之前都会进行数据的校验,比如用户注册时填写的邮箱地址是否符合规范、用户名长度的限制等等,不过这并不意味着服务端的代码可以免去数据验证的工作,用户也可能使用 HTTP 工具直接发送违法数据。为了保证数据的安全性,服务端的数据校验是必须的。

先理清概念:

  • JSR-303 是 JavaEE 6 中的一项子规范,又称作 Bean Validation,提供了针对 Java Bean 字段的一些校验注解,如@NotNull@Min等。JSR-349 是其升级版本,添加了一些新特性。
  • Hibernate Validator 是对这个规范的实现(与 ORM 框架无关),并在它的基础上增加了一些新的校验注解。
  • Spring 本身也有一个校验接口Validator,位于 org.springframework.validation 包下,但是使用这个接口需要进行硬编码,也就是手动校验,没有提供注解进行简化。为了给开发者提供便捷,Spring 也全面支持 JSR-303、JSR-349 规范,对 Hibernate Validation 进行二次封装,在 SpringMVC 模块中添加了自动校验机制,可以利用注解对 Java Bean 的字段的值进行校验,并将校验信息封装进特定的类中。

下面将介绍如何在 Spring 应用中使用 JSR-303 校验规范。

校验注解

1. JSR-303 包含的注解

注解名称 说明
@Null 被注解元素必须为 null
@NonNull 被注解元素必须不为 null
@AssertTrue 被注解元素必须为 true
@AssertFalse 被注解元素必须为 false
@Min(value) 被注解元素必须是一个值,并且不能小于指定的值
@Max(value) 被注解元素必须是一个值,并且不能大于指定的值
@DecimalMin(value) 被注解元素必须是一个数字,并且不能小于指定的值
@DecimalMax(value) 被注解元素必须是一个数字,并且不能大于指定的值
@Size(max=,min=) 被注解元素的大小必须在指定范围内
@Digits(integer,fraction) 被注解元素必须是一个数字,其值必须在指定范围内
@Past 被注解元素必须是一个过去的日期
@Future 被注解元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注解元素必须符合指定的正则表达式

2. Hibernate Validator 扩展的注解

注解名称 说明
@NotBlank(message=) 被注解的字符串必须非 null 且trim()后长度大于 0
@Email 被注解元素必须是电子邮箱地址
@Length(min=,max=) 被注解的字符串的长度必须在指定范围内
@NotEmpty 被注解元素(字符串、数组、集合等)必须非 null 且长度大于 0
@Range(min=,max=,message=) 被注解元素必须在合适的范围内
@URL 被注解元素必须是合法的 URL

用法

1. 依赖

pom.xml中引入 Hibernate Validator 需要的依赖包:


<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.10.Final</version> </dependency>

如果你使用的是 SpringBoot,那么只需引入spring-boot-starter-web即可,它的子依赖中包含了hibernate-validator和必要的数据绑定组件。

2. 实体类


public class Student { private Long id; @NotBlank(message = "名称不能为空") private String name; @Range(min = 10, max = 25, message = "年龄必须在10~25之间 ") private Integer age; @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$", message = "手机号码格式错误") @NotBlank(message = "手机号码不能为空") private String phone; @Email(message = "邮箱格式错误") private String email; // Getter/Setter }

每一个注解都包含了message字段,用于校验失败时作为提示信息。

3. 处理器方法

在处理器方法需要校验的参数上添加@Valid注解,就可以“激活”对它的校验操作,后面传入一个 BindingResult 类型的参数,用于获取校验失败情况下的反馈信息。


@PostMapping("/student") public ApiResult addStudent(@RequestBody @Valid Student student, BindingResult result) { if (result.hasErrors()) { for (ObjectError error : result.getAllErrors()) { System.out.println(error.getDefaultMessage(); } return ApiResult.error(); } studentService.insert(student); return ApiResult.success(); }

@Valid注解的参数和 BindingResult 参数必须是成对出现的,并且一前一后。

发送数据,进入上面的处理器:

控制台输出:

自定义校验

假如现在有一个需求是学生名字的内部不能含有空格,我们要如何自定义一个满足该要求的校验呢?

1. 自定义注解


@Target({FIELD, METHOD, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = NotContainSpaceValidator.class)
public @interface NotContainSpace {
//默认错误消息
String message() default "不能包含空格"; //分组
Class<?>[] groups() default {}; //负载
Class<? extends Payload>[] payload() default {}; //指定多个时使用,从而支持重复注解
@Target({FIELD, METHOD, PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List {
NotContainSpace[] value();
}
}

@Constraint指定这个注解真正的校验者类。

2. 校验者类

实现ConstraintValidator接口:


public class NotContainSpaceValidator implements ConstraintValidator<NotContainSpace, String> {
@Override
public void initialize(NotContainSpace notContainSpace) {
} @Override
public boolean isValid(String s, ConstraintValidatorContext context) {
if (s != null && s.trim().contains(" ")) {
// 获取默认提示信息
String constraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
System.out.println(constraintMessageTemplate); // 禁用默认提示信息
context.disableDefaultConstraintViolation(); // 设置提示语
context.buildConstraintViolationWithTemplate("Can not contain space").addConstraintViolation();
return false;
}
return true;
}
}

第一个泛型参数是表明校验的注解类型,第二个泛型参数是需要被校验的类型。

  • initialize:初始化事件方法
  • isValid:判断是否合法的方法

ConstraintValidatorContext这个上下文包含了校验中所有的信息,我们可以利用这个对象进行获取默认错误提示信息,禁用错误提示信息,改写错误提示信息等操作。

4. 使用


@NotContainSpace
private String name;

发送数据:

控制台输出:

可以看到NotContainSpaceValidator的执行是先于处理器方法的,并且禁用默认提示语、设置新的提示语等操作都是有效的。

分组校验

如果对同一个类,在不同的使用场景下有不同的校验规则,就可以使用分组校验。比如更新一条学生记录时,要求 id 不为 null 且大于 0:

我们上面在对方法参数添加自动校验时用的是@Valid注解,它是由 javax 提供的,其实 Spring Validation 校验框架还提供了@Validated注解。在检验 Controller 的入参是否符合规范时,它们的基本验证功能并没有多大区别。但是,现在要进行分组校验,就必须使用@Validated:


@PutMapping("/student")
public ApiResult updateStudent(@RequestBody @Validated({Student.Update.class}) Student student, BindingResult result) {
if (result.hasErrors()) {
for (ObjectError error : result.getAllErrors()) {
// do stuff
}
return ApiResult.error();
}
studentService.update(student);
return ApiResult.success();
}

不过注意,在@Validated中指定了分组,那么其他未分组的校验将会被忽略。

关于更多 @Valid 和 @Validated 的区别,可见文末参考链接。

END

Spring Validation 校验的更多相关文章

  1. 使用spring validation完成数据后端校验

    前言 数据的校验是交互式网站一个不可或缺的功能,前端的js校验可以涵盖大部分的校验职责,如用户名唯一性,生日格式,邮箱格式校验等等常用的校验.但是为了避免用户绕过浏览器,使用http工具直接向后端请求 ...

  2. Spring validation 后端校验【转】

    本文来自 下一秒升华 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/u013815546/article/details/77248003?utm_source=co ...

  3. 使用spring validation完成数据后端校验-自定义校验的注解-判断是否为空

    引入依赖 我们使用maven构建springboot应用来进行demo演示. <dependencies> <dependency> <groupId>org.sp ...

  4. 使用Spring Validation优雅地校验参数

    写得好的没我写得全,写得全的没我写得好 引言 不知道大家平时的业务开发过程中 controller 层的参数校验都是怎么写的?是否也存在下面这样的直接判断? public String add(Use ...

  5. Spring Validation最佳实践及其实现原理,参数校验没那么简单!

    之前也写过一篇关于Spring Validation使用的文章,不过自我感觉还是浮于表面,本次打算彻底搞懂Spring Validation.本文会详细介绍Spring Validation各种场景下 ...

  6. Spring Validation 表单校验

    最近开发一个项目,发现字段校验比较多,同事提出使用Validation校验能更方便一些,今天记录下来,供以后参考: 一.在pom.xml中添加依赖: <dependency> <gr ...

  7. Spring表单验证(Spring Validation)

    1.基本介绍 之前在项目中做的后台验证就是Spring Validation,最近闲下来了,就来整理一下. 从Spring3.0开始,Spring MVC中提供了对java校验的API支持.在Spri ...

  8. JSR303/JSR-349,hibernate validation,spring validation 之间的关系

    JSR303是一项标准,JSR-349是其的升级版本,添加了一些新特性,他们规定一些校验规范即校验注解,如@Null,@NotNull,@Pattern,他们位于javax.validation.co ...

  9. # SpringBoot使用Validation校验参数 ##

    SpringBoot使用Validation校验参数 一.简介 参考 (14条消息) 1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知_@decimalmax和@max_ ...

  10. springboot使用hibernate validator校验,Bean Validation校验

    第一个地址:springboot使用hibernate validator校验,Bean Validation校验

随机推荐

  1. CMake 属性之目录属性

    [写在前面] CMake 的目录属性是指在特定目录(及其子目录)范围内有效的设置. 这些属性不同于全局变量或目标(Target)属性,它们提供了一种机制,允许开发者为项目中的不同部分定义不同的构建行为 ...

  2. 活动预告 | 中国数据库联盟(ACDU)中国行第二站定档杭州,邀您探讨数据库技术与实践!

    数据库技术一直是信息时代中不可或缺的核心组成部分,随着信息量的爆炸式增长和数据的多样化,其重要性愈发凸显.作为中国数据库联盟(ACDU)的品牌活动之一,[ACDU 中国行]在线下汇集数据库领域的行业知 ...

  3. document.write 和 innerHTML 的区别

    a document.write 是整个页面的内容,会重写页面b innerHTML 是某个元素的内容,只有给body的innerHTML设置内容才会重写页面

  4. 1. java + react 实现 HRM

    1. 云服务的三种方式 1.1 IAAS 基础设施即服务 ,只会提供基础的设施,eg:服务器,网络等 : 1.2 PAAS 平台即服务 ,提供平台,可以把自己写好的代码部署到平台上 : 1.3 SAA ...

  5. 01-react的基本使用

    // 导入react和react-dom包 类似 vue 中的 import vue from 'vue' import react from 'react' // 内部的组件 import reac ...

  6. 38. data为什么是一个函数

    vue中的data为什么是返回对象的函数,而不是直接使用对象形式 : 我们复用组件的时候,要求每一份data数据之间是独立的,不能互相影响,如果写成对象的形式所有的组件使用一份data数据 ,如果使用 ...

  7. 云原生爱好者周刊:STUNner 助你在 K8s 集群中使用 WebRTC 服务

    开源项目推荐 STUNner 目前大多数内网穿透服务都依赖于 STUN 服务或者 TURN 服务,但这些服务大多数都是公用的,即使是私有化部署,也没法迁移到 Kubernetes 的环境中,因为 Ku ...

  8. SQLSEVER 实现货币数字转中文汉字

    SQLSEVER 实现数字转换成中文(货币) -- ============================================= -- Author: LearnerPing -- Cr ...

  9. PHP中$GLOBALS和global的区别,简单了解符号表、zval

    前言 单位里有一套老代码,写了这么一个换库逻辑. function conn() { global $conn; if ($conn) { unset($conn); } $conn = mysqli ...

  10. 一文彻底弄懂并解决Redis的缓存雪崩,缓存击穿,缓存穿透

    缓存雪崩.缓存击穿.缓存穿透是分布式系统中使用缓存时,常遇到的三类问题,都会对系统性能和稳定性产生严重影响.下面将详细介绍这三者的定义.产生原因.危害以及常见的解决方案. 1. 缓存雪崩 1.1 定义 ...