参数校验JakartaBeanValidation学习
1.背景
我们在平时的学习与⼯作中,都需要对参数进⾏校验,⽐如在注册时,⽤户名密码不能为空,⽤户名长度必须⼩于10等等。虽然有些校验在前端页⾯会进⾏验证,但是后端为了增加健壮性也需要对这些参数进⾏判断(⽐如绕过前端页⾯⽽直接调⽤了接⼝,参数的合法性未知),可能就会在controller或者service中就会有如下代码的出现
package com.ller;
import com.beemo.ity.Student;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.Objects;
@RestController
public class DemoController {
@RequestMapping("/demo")
public String saveDemo(@RequestBody Student student) {
if (StringUtils.Name())) {
return "学⽣名称不能为空";
}
if (Name().length() > 10) {
return "学⽣名称长度不能超过10位";
}
if (Objects.Age())) {
return "学⽣年龄不能为空";
}
if (Age() <= 0) {
return "学⽣年龄不能为负数";
}
if (Objects.Number())) {
return "学号不能为空";
}
if (Number().length() != 10) {
return "学号长度必须为10";
}
// 其他判断
/
/ 调⽤service的⽅法等
return "ok";
}
@Data
class Student {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
validation框架/**
* 学号
*/
private String number;
}
}
从例⼦中可以看到,这仅仅是⼀个实体类3个字段的简单验证,就已经占据了很多的篇幅,也需要我们进⾏⼿动编写这种判断代码,⽐较费时,代码读起来也没什么营养,⼤部分都是在判断合法性,等我们真正读到想要的业务逻辑代码可能需要往下翻好久,那么有没有办法能够让我们更简洁更优雅的去验证这些参数呢
2. Jakarta Bean Validation
2.1 Jakarta Bean Validation简介
⾸先要知道Jakarta就是Java更名之后的名称,Jakarta Bean Validation也就是Java Bean Validation,是⼀套Java的规范,它可以
通过使⽤注解的⽅式在对象模型上表达约束
以扩展的⽅式编写⾃定义约束
提供了⽤于验证对象和对象图的API
提供了⽤于验证⽅法和构造⽅法的参数和返回值的API
报告违反约定的集合
运⾏在Java SE,并且集成在Jakarta EE8中
例如:
public class User {
private String email;
@NotNull @Email
public String getEmail() {
return email;
}
public void setEmail(String email) {
}
public class UserService {
public void createUser(@Email String email, @NotNull String name) {
...
}
}
虽然可以⼿动运⾏校验,但更加⾃然的做法是让其他规则和框架在适时对数据进⾏校验(⽤户在表⽰框架中进⾏输⼊,业务服务通过CDI执⾏,实体通过JPA插⼊或者更新)
换句话说,即运⾏⼀次,到处约束
2.2 相关⽹址
在2020年2⽉份已经发布了3.0.0-M1版本
其中Jakarta Bean Validation只是⼀套标准,我们需要使⽤其他组织机构提供的实现来进⾏验证,官⽅⽀持的为Hibernate Validator
3. 动⼿实践
3.1 所需环境
这⾥JDK使⽤了JDK1.8,使⽤maven进⾏所需jar⽂件依赖,使⽤springboot搭建框架脚⼿架,使⽤lom
bok简化代码
如果⽤的不是这⼏个可以适当修改,⼤同⼩异,⽽且springboot以及或其他依赖的版本每天都在变化,各个版本之间难免有或多或少的差别,可能细节处与本⽂章有所不同,需要⼤家知晓,并且根据⾃⼰的版本进⾏调整(⽐如spring-boot-starter-parent版本2.2.7与2.3.0在验证异常时返回json格式与内容就有很⼤不同)
3.2 搭建空框架
使⽤spring initializr创建springboot项⽬,依次选择添加web、validation以及lombok模块,⽣成的l依赖如下。我这⾥spring-boot-starter-parent的版本为2.3.0,再添加其他所需的pom依赖
...
<!-- spring-boot版本 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
...
<!-- web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 验证模块,hibernate-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!-- guava -->
<dependency>
<groupId&le.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
3.3 编写代码
编写背景:模拟英雄联盟游戏的技能与英雄的保存
这⾥的命名遵循外服名称⽽不是国服直译,例如英雄为champion⽽不是hero,技能为ability⽽不是skill
3.3.1 实体类
英雄
package com.beemo.ity;
import lombok.Data;
import straints.NotBlank;
import straints.NotNull;
/**
* 英雄entity
*/
@Data
public class Champion {
/**
* 英雄名称
*/
@NotBlank(message = "英雄名称不能为空")
private String name;
/**
* 英雄头衔
@NotBlank(message = "英雄头衔不能为空")
private String title;
/**
* 英雄描述
*/
@NotBlank(message = "英雄描述不能为空")
private String description;
/**
* 英雄类型
* 坦克、刺客、射⼿、法师、辅助以及战⼠
*/
@NotNull(message = "英雄类型不能为空")
private Byte type;
}
技能entity
package com.beemo.ity;
import lombok.Data;
import straints.NotBlank;
import straints.NotNull;
/**
* 技能
*/
@Data
public class Ability {
/**
* 技能名称
*/
@NotBlank(message = "技能名称不能为空")
private String name;
/**
* 技能描述
*/
@NotBlank(message = "技能描述不能为空")
private String description;
/**
* 技能类型
* 例如魔法值、怒⽓、能量等
*/
@NotNull(message = "技能类型不能为空")
private Byte type;
}
3.3.2 控制层
英雄controller
package com.beemo.ller;
import com.beemo.ity.Champion;
import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequestMapping("/demo2/champion")
@Validated
public class ChampionController {
/**
* 保存
* @param entity 要保存的英雄实体
* @return保存结果
*/
@PostMapping("save")
public String save(@Valid @RequestBody Champion entity) {
// 调⽤service等
return "ok";
}
}
技能controller
package com.beemo.ller;
import com.beemo.ity.Ability;
import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequestMapping("/demo2/ability")
@Validated
public class AbilityController {
/**
* 保存
* @param entity 要保存的技能实体
* @return保存结果
*/
@PostMapping("save")
public String save(@Valid @RequestBody Ability entity) {
// 调⽤service等
return "ok";
}
}
3.3.3 测试
使⽤postman或其他⼯具发送POST请求,进⾏验证,我们直接输⼊我们参数直接传⼀个内容为空的json,查看结果
可以看到,这⾥返回了400异常,意为参数错误
我们再把所有参数补全,再试⼀下
可以看到,如果我们把参数补全之后,返回的是“ok”,即进⼊controller执⾏该⽅法。
那么,例⼦中添加的⼏个注解都是什么意思,有什么作⽤,⽽且注解中写的message信息在验证后并没有输出,那么我们怎么样输出这些message呢
4. 注解含义
4.1 开启验证
⾸先我们看controller类最上⽅,我们标注了@Validataed(这⾥可以去掉,不⽤在控制类上添加该注解),该注解的含义是:这个类要启⽤参数校验。在save⽅法的参数中标注了@Valid,含义为我们要对紧跟的实体进⾏校验,⽽具体校验的内容,为实体类中的我们的定义的约束
以Ability类举例,在name字段上⽅标记了@NotBlank,意为定义了该字段不允许为空的约束,如果name为空,校验就不通过,就会返回我们之前碰到的400异常。⽽type字段也标注了
@NotNull,也定义了该字段不允许为空的约束,具体的区别以及其他内置的约束如3.5所⽰
4.2 内置约束
内置约束位于straints包内,列表如下
4.2.1 @Null
被标注元素必须为null
接收任意类型
⽐如在创建⼀个英雄时,ID需要由数据库⾃增⽣成,⽽不是我们⾃定义,那么该我们在接收前台传递的json时就必须为空
4.2.2 @NotNull
被标注元素必须不为null
接收任意类型
定义⼀个字段不能为空,例如技能类型或者英雄名称
4.2.3 @AssertTrue
被标注元素必须true
⽀持的类型为boolean以及Boolean
null被认为是有效的
要么为null,否则必须为true
4.2.4 @AssertFalse
被标注元素必须false
⽀持的类型为boolean以及Boolean
null被认为是有效的
要么为null,否则必须为false
4.2.5 @Min
被标注元素必须为是⼀个数字,其值必须⼤于等于指定的最⼩值
⽀持的类型为BigDecimal 、BigInteger 、byte 、short 、int 、long 以及各⾃的包装类
注意double以及float由于舍⼊错误⽽不被⽀持
null被认为是有效的
4.2.6 @Max
被标注元素必须为是⼀个数字,其值必须⼩于等于指定的最⼤值
⽀持的类型为BigDecimal 、BigInteger 、byte 、short 、int 、long 以及各⾃的包装类
注意double以及float由于舍⼊错误⽽不被⽀持
null被认为是有效的
4.2.7 @DecimalMin
被标注元素必须为是⼀个数字,其值必须⼤于等于指定的最⼩值
⽀持的类型为BigDecimal 、BigInteger 、CharSequence、byte 、short 、int 、long 以及各⾃的包装类
注意double以及float由于舍⼊错误⽽不被⽀持
null被认为是有效的
4.2.8 @DecimalMax
被标注元素必须为是⼀个数字,其值必须⼩于等于指定的最⼤值
⽀持的类型为BigDecimal 、BigInteger 、CharSequence、byte 、short 、int 、long 以及各⾃的包装类
注意double以及float由于舍⼊错误⽽不被⽀持
null被认为是有效的
4.2.9 @Negative
被标注元素必须为是⼀个严格意义上的负数(即0被认为是⽆效的)
⽀持的类型为BigDecimal 、BigInteger、byte 、short 、int 、long 、float、double以及各⾃的包装类
null被认为是有效的
4.2.10 @NegativeOrZero
被标注元素必须为是负数或者0
⽀持的类型为BigDecimal 、BigInteger、byte 、short 、int 、long 、float、double以及各⾃的包装类
null被认为是有效的
4.2.11 @Positive
被标注元素必须为是⼀个严格意义上的正数(即0被认为是⽆效的)
⽀持的类型为BigDecimal 、BigInteger、byte 、short 、int 、long 、float、double以及各⾃的包装类
null被认为是有效的
4.2.12 @PositiveOrZero
被标注元素必须为是正数或者0
⽀持的类型为BigDecimal 、BigInteger、byte 、short 、int 、long 、float、double以及各⾃的包装类
null被认为是有效的
4.2.13 @Size
被标注元素的⼤⼩必须在指定的边界区间
⽀持的类型为CharSequence(计算字符序列的长度)、Collection(计算集合的⼤⼩)、Map(计算map的⼤⼩)、Array(计算数组的长度)
null被认为是有效的
4.2.14 @Digits
被标注元素必须是在可接受范围内的数字
⽀持的类型为BigDecimal 、BigInteger、CharSequence、byte 、short 、int 、long 以及各⾃的包装类
null被认为是有效的
4.2.15 @Past
被标注元素必须是过去的某个时刻、⽇期或者时间
“现在”的概念是附加在Validator或者ValidatorFactory中的ClockProvider定义的,默认的ClockProvider根据虚拟机定义了当前时间,如果需要的话,会应⽤当前默认时区
⽀持的类型为java.util.Date 、java.util.Calendar、java.time.Instant、java.time.LocalDate 、java.time.LocalDateTime 、java.time.LocalTime} 、java.time.MonthDay 、java.time.OffsetDateTime 、java.time.OffsetTime 、java.time.Year 、java.time.YearMonth 、java.time.ZonedDateTime 、java.time.chrono.HijrahDate 、
java.time.chrono.JapaneseDate 、java.time.chrono.MinguoDate、java.time.chrono.ThaiBuddhistDate 以及各⾃的包装类
null被认为是有效的
4.2.16 @PastOrPresent
被标注元素必须是过去或现在的某个时刻、⽇期或者时间
“现在”的概念是附加在Validator或者ValidatorFactory中的ClockProvider定义的,默认的ClockProvider
根据虚拟机定义了当前时间,如果需要的话,会应⽤当前默认时区“现在”的概念相对的定义在使⽤的约束上,例如,如果约束在Year上,那么现在表⽰当前年份
⽀持的类型为java.util.Date 、java.util.Calendar、java.time.Instant、java.time.LocalDate 、java.time.LocalDateTime 、java.time.LocalTime} 、java.time.MonthDay 、java.time.OffsetDateTime 、java.time.OffsetTime 、java.time.Year 、java.time.YearMonth 、java.time.ZonedDateTime 、java.time.chrono.HijrahDate 、
java.time.chrono.JapaneseDate 、java.time.chrono.MinguoDate、java.time.chrono.ThaiBuddhistDate 以及各⾃的包装类
null被认为是有效的
4.2.17 @Future
被标注元素必须是未来的某个时刻、⽇期或者时间
“现在”的概念是附加在Validator或者ValidatorFactory中的ClockProvider定义的,默认的ClockProvider根据虚拟机定义了当前时间,如果需要的话,会应⽤当前默认时区
⽀持的类型为java.util.Date 、java.util.Calendar、java.time.Instant、java.time.LocalDate 、java.time.LocalDateTime 、java.time.LocalTime} 、java.time.MonthDay 、java.time.OffsetDateTime 、java.time.OffsetTime 、java.time.Year 、java.time.YearMonth 、java.time.ZonedDateTime 、java.time.chrono.HijrahDate 、
java.time.chrono.JapaneseDate 、java.time.chrono.MinguoDate、java.time.chrono.ThaiBuddhistDate 以及各⾃的包装类
null被认为是有效的
4.2.18 @FutureOrPresent
被标注元素必须是未来或现在的某个时刻、⽇期或者时间
“现在”的概念是附加在Validator或者ValidatorFactory中的ClockProvider定义的,默认的ClockProvider根据虚拟机定义了当前时间,如果需要的话,会应⽤当前默认时区“现在”的概念相对的定义在使⽤的约束上,例如,如果约束在Year上,那么现在表⽰当前年份
⽀持的类型为java.util.Date 、java.util.Calendar、java.time.Instant、java.time.LocalDate 、java.time.LocalDateTime 、java.time.LocalTime} 、java.time.MonthDay 、java.time.OffsetDateTime
、java.time.OffsetTime 、java.time.Year 、java.time.YearMonth 、java.time.ZonedDateTime 、java.time.chrono.HijrahDate 、
java.time.chrono.JapaneseDate 、java.time.chrono.MinguoDate、java.time.chrono.ThaiBuddhistDate 以及各⾃的包装类
null被认为是有效的
4.2.19 @Pattern
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论