JSR 303 进行后台数据校验
一、JSR 303
1、什么是 JSR 303?
JSR 是 Java Specification Requests 的缩写,即 Java 规范提案。
存在各种各样的 JSR,简单的理解为 JSR 是一种 Java 标准。
JSR 303 就是数据检验的一个标准(Bean Validation (JSR 303))。
参考:
https://www.jianshu.com/p/554533f88370
2、为什么使用 JSR 303?
处理一段业务逻辑,首先要确保数据输入的正确性,所以需要先对数据进行检查,保证数据在语义上的正确性,再根据数据进行下一步的处理。
前端可以通过 js 程序校验数据是否合法,后端同样也需要进行校验。而后端最简单的实现就是直接在业务方法中对数据进行处理,但是不同的业务方法可能会出现同样的校验操作,这样就出现了数据的冗余。为了解决这个情况,JSR 303 出现了。
JSR 303 使用 Bean Validation,即在 Bean 上添加相应的注解,去实现数据校验。这样在执行业务方法前,都会根据注解对数据进行校验,从而减少自定义的校验逻辑,减少代码冗余。
3、JSR 303 常见操作?
(1)可以通过简单的注解校验 Bean 属性,比如 @NotNull、@Null 等。
(2)可以通过 Group 分组自定义需要执行校验的属性。
(3)可以自定义注解并指定校验规则。
(4)支持基于 JSR 303 的实现,比如 Hibernate Validator(额外添加一些注解)。
二、演示 JSR303 的简单使用
1、构建一个 SpringBoot 项目用来演示
(1)构建一个 SpringBoot 项目,以及使用 EasyCode 逆向生成相关的代码。
参考地址:
https://www.cnblogs.com/l-y-h/p/12781586.html
模板代码地址:
https://gitee.com/lyh-man/fast-template.git
(2)工具使用详情:
SpringBoot 2.2.6 + JDK 1.8 + mysql 1.8 搭建基本开发环境
IDEA + EasyCode + Lombok 插件 逆向生成基本代码
Postman 发送请求,测试接口
2、未使用 JSR303 相关注解时
没用 JSR 303 相关注解时,需要手动在业务方法里写处理数据的逻辑。
修改 Controller ,简单测试一下未使用 JSR 303 相关注解时的做法。
@RestController
@RequestMapping("api")
public class EmpController {
@Resource
private EmpService empService; @PostMapping("/emp")
public Result createEmp(@RequestBody Emp emp) {
if (emp.getId() == null || emp.getName() == null) {
return Result.error().message("数据不存在");
}
return Result.ok().data("items", emp).message("数据插入成功");
}
}
使用 postman 测试该接口,当 id 不存在时,会被检测到。
id,name 都存在时,不会被捕获。
这里只是简单的测试一下逻辑,真实的数据检测肯定比这复杂的多,然后每个方法都需要写不同的数据处理逻辑,这样就会造成数据的冗余。而使用 JSR303 的相关注解,就很简单,继续往下看。
3、使用 JSR 303 相关注解处理逻辑
(1)使用步骤:
Step1:
在相关的 Bean 上标注需要处理的注解,并指定需要提示的信息(若不指定,会从默认配置文件中读取默认的信息)。
Step2:
在相关的方法上,使用 @Valid 注解(或者 @Validated 指定组名)标记需要被校验的数据,否则会不生效。
注意:
检测到数据异常后,系统会向外抛出异常,如果做了统一异常处理,可以根据 postman 测试的结果,找到控制台打印出的 相应的异常,并处理。
Step3:
处理异常。使用 BindingResult 可以获取到检测结果,然后进行处理。
也可以使用 全局统一异常 处理(@RestControllerAdvice 与 @ExceptionHandler),处理检测结果。
注:
统一异常处理参考:https://www.cnblogs.com/l-y-h/p/12781586.html#_label2
(2)使用:
Step1:
在相关的 Bean 上标注注解,并写上指定信息。
import lombok.Data; import javax.validation.constraints.NotNull;
import java.io.Serializable; @Data
public class Emp implements Serializable {
private static final long serialVersionUID = 281903912367009575L; @NotNull(message = "id 不能为 null")
private Integer id; @NotNull(message = "name 不能为 null")
private String name; private Double salary; private Integer age; private String email;
}
Step2:
修改 Controller 方法,使用 @Valid 注解标记需要检测的数据。
@RestController
@RequestMapping("api")
public class EmpController {
@Resource
private EmpService empService; @PostMapping("/emp")
public Result createEmp(@Valid @RequestBody Emp emp) {
return Result.ok().data("items", emp).message("数据插入成功");
}
}
Step3:
使用 postman 测试一下。会抛出 MethodArgumentNotValidException 异常。
控制台打印的信息:
Step4:
可以使用 BindingResult 去处理捕获到的数据并进行相关处理。
@RestController
@RequestMapping("api")
public class EmpController {
@Resource
private EmpService empService; @PostMapping("/emp")
public Result createEmp(@Valid @RequestBody Emp emp, BindingResult result) {
if (result.hasErrors()) {
Map<String, String> map = new HashMap<>();
// 获取校验结果,遍历获取捕获到的每个校验结果
result.getFieldErrors().forEach(item ->{
// 获取校验的信息
String message = item.getDefaultMessage();
String field = item.getField();
// 存储得到的校验结果
map.put(field, message);
});
return Result.error().message("数据不合法").data("items", map);
}
return Result.ok().data("items", emp).message("数据插入成功");
}
}
使用 Postman 测试。
Step5:
通过上面的步骤,已经可以捕获异常、处理异常,但是每次都是在业务方法中手动处理逻辑,这样的实现,代码肯定会冗余。可以将其抽出,使用 统一异常处理,每次异常发生时,将其捕获。
根据 Step3 可以看到会抛出 MethodArgumentNotValidException 异常,所以需要将其捕获。
需要使用 @RestControllerAdvice 与 @ExceptionHandler。
@RestControllerAdvice
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(getClass()); @ExceptionHandler(MethodArgumentNotValidException.class)
public Result handlerValidException(MethodArgumentNotValidException e) {
logger.error(e.getMessage(), e);
BindingResult result = e.getBindingResult();
Map<String, String> map = new HashMap<>();
// 获取校验结果,遍历获取捕获到的每个校验结果
result.getFieldErrors().forEach(item ->{
// 存储得到的校验结果
map.put(item.getField(), item.getDefaultMessage());
});
return Result.error().message("数据校验不合法").data("items", map);
}
}
相应的业务方法里,不需要再用 BindingResult 去处理数据了(即 Step2 的状态)。
@RestController
@RequestMapping("api")
public class EmpController {
@Resource
private EmpService empService; @PostMapping("/emp")
public Result createEmp(@Valid @RequestBody Emp emp) {
return Result.ok().data("items", emp).message("数据插入成功");
}
}
使用 Postman 测试。
4、JSR 303 分组校验
(1)为什么使用 分组校验?
通过上面的过程,可以了解到单个方法的校验规则。
如果出现多个方法,都需要校验 Bean,且校验规则不同的时候,怎么办呢?
分组校验就可以去解决该问题,每个分组指定不同的校验规则,不同的方法执行不同的分组,就可以得到不同的校验结果。
(2)基本认识
JSR 303 的每个注解都默认具备三个属性:
message 用来定义数据校验失败后的提示消息,默认读取配置文件的内容。
全局搜索 ValidationMessages.properties,可以看到默认的信息。
groups 用来定义分组,其是一个 class 数组,可以指定多个分组。
String message() default "{javax.validation.constraints.NotNull.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { };
(3)使用分组步骤:
Step1:
定义一个空接口,用于指定分组,内部不需要任何实现。
Step2:
指定 注解时,通过 groups 指定分组。用于指定在某个分组条件下,才去执行校验规则。
Step3:
在相关的业务方法上,通过 @Validated 注解指定分组,去指定校验。
注:
使用分组校验后,Bean 注解上若不指定分组,则不会执行校验规则。
(4)使用:
Step1:
创建分组接口。
创建两个分组接口 AddGroup、UpdateGroup。
其中:
AddGroup 用于指定 添加数据 时的校验规则(比如:id、name 均不为 null)。
UpdateGroup 用于指定 修改数据 时的校验规则(比如:name 不允许为 null)。
Step2:
给 Bean 添加注解,并指定分组信息。
@Data
public class Emp implements Serializable {
private static final long serialVersionUID = 281903912367009575L; @NotNull(message = "id 不能为 null", groups = {AddGroup.class})
private Integer id; @NotNull(message = "name 不能为 null", groups = {AddGroup.class, UpdateGroup.class})
private String name; private Double salary; private Integer age; private String email;
}
Step3:
在业务方法上,通过 @Validated 注解指定分组,去指定校验。
如下例,定义两个方法,Post 请求会触发 createEmp 方法,Put 请求会触发 UpdateEmp 方法。
@RestController
@RequestMapping("api")
public class EmpController {
@Resource
private EmpService empService; @PostMapping("/emp")
public Result createEmp(@Validated({AddGroup.class}) @RequestBody Emp emp) {
return Result.ok().data("items", emp).message("数据插入成功");
} @PutMapping("/emp")
public Result UpdateEmp(@Validated({UpdateGroup.class}) @RequestBody Emp emp) {
return Result.ok().data("items", emp).message("数据插入成功");
}
}
Step4:
使用 Postman 测试,发送 Post 请求,触发 createEmp 方法,执行 AddGroup 校验规则。
检测 id、name 是否合法。
发送 Put 请求,触发 UpdateEmp 方法,执行 UpdateGroup 校验规则。
只检测 name 是否合法。
5、JSR 303 自定义校验注解
(1)为什么使用自定义校验注解?
上面的注解满足不了业务需求时,可以自定义校验注解,自定义校验规则。
(2)步骤:
Step1:
需要自定义一个校验注解。
可以创建一个 ValidationMessages.properties 用于保存默认的 message 信息。
Step2:
需要自定义一个校验器,即自定义校验规则。
实现 ConstraintValidator 接口,并重写相关方法。
注:
initialize 方法用于初始化,可以获取 自定义的属性的值。
isValid 方法用于校验,可以获取到实际的值,然后与自定义的属性值进行比较。
Step3:
将校验注解 与 校验器 关联起来。
@Constraint(validatedBy = {TestValidConstraintValidator.class})
(3)使用:
如下例,自定义一个校验规则,判断数据长度是否合法。
默认为 String 属性,当 String 为 Null 或者 长度大于 5 时,校验不通过。
可以自定义 长度。
Step1:
自定义一个校验注解,@TestValid,用于判断一个值的长度是否合法。
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME; /**
* 用于判断一个值的长度是否合法
*/
@Target({FIELD})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {TestValidConstraintValidator.class})
public @interface TestValid {
String message() default "{com.lyh.test.TestValid.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; /**
* 返回一个长度
* @return 默认为 5
*/
int length() default 5;
}
配置文件内容:
Step2:
自定义一个校验器TestValidConstraintValidator, 用于检测值是否合法。
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext; /**
* 实现 ConstraintValidator 接口,
* 其中 ConstraintValidator 的泛型,一个需要指定自定义的注解,一个需要指定需要获取的值的类型。
* 比如:
* ConstraintValidator<TestValid, String> 中
* TestValid 表示自定义注解
* String 表示获取的值的类型
* 即定义规则,判断一个 String 的值的长度是否满足条件
*/
public class TestValidConstraintValidator implements ConstraintValidator<TestValid, String> { /**
* 用于保存自定义的(默认)长度
*/
private int length; /**
* 初始化方法,获取默认数据
* @param constraintAnnotation 注解对象
*/
@Override
public void initialize(TestValid constraintAnnotation) {
length = constraintAnnotation.length();
} /**
* 自定义校验规则,如果 String 为 Null 或者 长度大于 5,则校验失败(返回 false)
* @param value 需要校验的值
* @param context
* @return true 表示校验成功,false 表示校验失败
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value == null ? false : length > value.length();
} }
Step3:
使用注解。
@Data
public class Emp implements Serializable {
private static final long serialVersionUID = 281903912367009575L; @NotNull(message = "id 不能为 null", groups = {AddGroup.class})
private Integer id; @TestValid(groups = {AddGroup.class})
@NotNull(message = "name 不能为 null", groups = {AddGroup.class, UpdateGroup.class})
private String name; private Double salary; private Integer age; @TestValid(length = 10, message = "值不能为 Null 且长度不超过 10", groups = {AddGroup.class})
private String email;
}
使用 Postman 测试。
name、email 都不存在时,会被捕获数据异常。
name 数据不合法、email 数据合法时,name 会被捕获。
三、JSR 303 相关注解
1、空检查相关注解
注解 注解详情
@Null 被指定的注解元素必须为 Null
@NotNull 任意类型,不能为 Null,但可以为空,比如空数组、空字符串。
@NotBlank 针对字符串,不能为 Null,且去除前后空格后的字符串长度要大于 0。
@NotEmpty 针对字符串、集合、数组,不能为 Null,且长度要大于 0。
2、长度检查
注解 注解详情
@Size 针对字符串、集合、数组,判断长度是否在给定范围内。
@Length 针对字符串,判断长度是否在给定范围内。
3、布尔值检查
注解 注解详情
@AssertTrue 针对布尔值,用来判断布尔值是否为 true
@AssertFalse 针对布尔值,用来判断布尔值是否为 false
4、日期检查
注解 注解详情
@Past 针对日期,用来判断当前日期是否为 过去的日期
@Future 针对日期,用来判断当前日期是否为 未来的日期
5、数值检查
注解 注解详情
@Max(value) 针对字符串、数值,用来判断是否小于等于某个指定值
@Min(value) 针对字符串、数值,用来判断是否大于等于某个指定值
6、其他
注解 注解详情
@Pattern 验证字符串是否满足正则表达式
@Email 验证字符串是否满足邮件格式
@Url 验证是否满足 url 格式
JSR 303 进行后台数据校验的更多相关文章
- spring的后台数据校验
数据校验对于开发项目来说是必须的.校验一般分为前台校验和后台校验,前台校验是必须要做的,后台校验是可选的.后台校验相对前台校验来说配置起来一般更复杂.前台校验通过js做,前台校验一般非常容易绕过.sp ...
- 使用JSR-303进行后台数据校验
一.在SringMVC中使用 使用注解 1.准备校验时使用的JAR validation-api-1.0.0.GA.jar:JDK的接口: hibernate-validator-4.2.0.Fina ...
- 后台数据校验-BeanCheck
package com.ldf.domain; import java.text.ParseException; public class UserCheck { //从表单获取的数据 private ...
- jquery.validate 验证(支持前台js验证通过,然后ajax后台数据校验)二
jquery.validate 为啥 源码 里面 规定 dataType: "json" 呢 因为 他配套的 是 messages 下面 的 remote 属性 验证失 ...
- Hibernate数据校验简介
我们在业务中经常会遇到参数校验问题,比如前端参数校验.Kafka消息参数校验等,如果业务逻辑比较复杂,各种实体比较多的时候,我们通过代码对这些数据一一校验,会出现大量的重复代码以及和主要业务无关的逻辑 ...
- SpringMVC中的 JSR 303 数据校验框架说明
JSR 303 是java为Bean数据合法性校验提供的标准框架,它已经包含在JavaEE 6.0中. JSR 303 通过在Bean属性上标注类似于@NotNull.@Max等标准的注解指定校验规则 ...
- Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验,使用消息资源文件对消息国际化
导包和配置 导入 JSR 303 的包.hibernate valid 的包 <dependency> <groupId>org.hibernate.validator< ...
- JSR教程2——Spring MVC数据校验与国际化
SpringMVC数据校验采用JSR-303校验. • Spring4.0拥有自己独立的数据校验框架,同时支持JSR303标准的校验框架. • Spring在进行数据绑定时,可同时调用校验框架完成数据 ...
- 用spring的@Validated注解和org.hibernate.validator.constraints.*的一些注解在后台完成数据校验
这个demo主要是让spring的@Validated注解和hibernate支持JSR数据校验的一些注解结合起来,完成数据校验.这个demo用的是springboot. 首先domain对象Foo的 ...
随机推荐
- 在WinForms里嵌入MediaPlayer的一些版本问题, tlbimp导入, 以及不导入而纯用C#+字符串来动态调用.
网上很多写使用WindowsMediaPlayer WMP控件的文章. 大多数都是从工具栏或COM导入. 最近正在做的CEF整合Asp.Net Core Blazor server side的过程中, ...
- react后台管理系统路由方案及react-router原理解析
最近做了一个后台管理系统主体框架是基于React进行开发的,因此系统的路由管理,选用了react-router(4.3.1)插件进行路由页面的管理配置. 实现原理剖析 1.hash的方式 ...
- Cron表达式,springboot定时任务
详细请看这篇博客 参考:https://blog.csdn.net/belonghuang157405/article/details/83410197 Cron表达式是一个字符串,字符串以5或6个空 ...
- Servlet 执行时一般实现哪几个方法?
public void init(ServletConfig config): public ServletConfig getServletConfig(): public String getSe ...
- Jquery封装:下拉框插件
代码如下: ;(function ($, window) { $.fn.addSelect = function (options) { //合并传入与默认的参数 var opts = $.exten ...
- JS中的各类运算符
2020-04-15 JS中的各类运算符 // 假设有如下代码,那么a(10)的返回结果是?( ) function a(a) { a^=(1<<4)-1; return a; } // ...
- cb30a_c++_STL_算法_查找算法_(3)search_find_end
cb30a_c++_STL_算法_查找算法_(3)search_find_endsearch()pos = search(ideq.begin(), ideq.end(), ilist.begin() ...
- MySQL的使用方法和视图、索引、以及存储过程的一些简单方法
一,基本概念 1, 常用的两种引擎: (1) InnoDB a,支持ACID,简单地说就是支持事务完整性.一致性: b,支持行锁,以及类似ORACLE的一 ...
- 微信小程序之后端处理
首先,来看一下后端的关系图: 这边主要介绍PHP的一些基础语法等等,关于将php代码部署到SAE新浪云,大家可以参考这个链接:https://www.cnblogs.com/dhx96/p/65617 ...
- Docker Dockerfile 指令详解与实战案例
Dockerfile介绍及常用指令,包括FROM,RUN,还提及了 COPY,ADD,EXPOSE,WORKDIR等,其实 Dockerfile 功能很强大,它提供了十多个指令. Dockerfile ...