当前位置:网站首页>JSR303以及常见Validator实现
JSR303以及常见Validator实现
2022-06-30 15:48:00 【伊布拉西莫】
《乐之者java-validator》学习笔记
前言
- Bean Validation - https://beanvalidation.org/, 它是
JAVAEE的规范,无具体实现
。 - Hibernate Validator: - https://hibernate.org/validator/,它是
具体的实现
。
传统参数校验
假设有登录场景,登录参数为LoginForm
:
@Data
public class LoginForm {
private String username;
private String password;
}
使用校验逻辑如下:
public void login(LoginForm form){
if(form == null){
throw new RuntimeException("login登录时参数为null");
}
if(form.getUsername() == null || form.getUsername().trim().isEmpty()){
throw new RuntimeException("login登录时为username不合规");
}
if(form.getPassword() == null || form.getPassword().trim().isEmpty()){
throw new RuntimeException("login登录时为password不合规");
}
//todo business....
}
JSR303 规范
什么是Java EE 规范
java EE 规范只有API,没有实现
。 具体的实现,由各厂商实现。例如:
javax.sql.*
javax.servlet
javax.jmx
- …
java se自带的规范
并不是仅有这些,上述为javaee常见的一些规范。
对于一些上面没有的,则需要手动引入api依赖。
java ee规范由谁制定
Java Community Process
从javax 到 jakarta
oracle收购sun之后,oralce开始实行java商业授权。迫于java开源社区的压力,oracle将javax规范
捐献给了eclipse
.
但是不允许使用javax.*
,所以才有了现在的jakarta。
所以以后会出现:jakarta.sq.....l
不要跟
apache.jakarta
混为一谈。
JSR303的实现 - Hibernate
maven依赖
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.18.Final</version>
<exclusions>
<exclusion>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- hibernate-validator中已经包含了:validation-api <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.1.Final</version> </dependency> -->
<!-- 等价于jakarta.validation-api <dependency> <groupId>jakarta.validation</groupId> <artifactId>jakarta.validation-api</artifactId> <version>2.0.1</version> </dependency> -->
<!-- 用于解析message中的el表达式 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>9.0.29</version>
</dependency>
UserInfo
@Data
public class UserInfo {
private Long id;
private String name;
private Integer age;
private String email;
private LocalDate birthdate;
}
ValidatorUtil
public class ValidatorUtil {
private static Validator validator;
static {
//涉及到SPI
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
public static void valid(UserInfo userInfo){
//如果校验对象userInfo 不通过,则存在校验不通过信息
Set<ConstraintViolation<UserInfo>> set = validator.validate(userInfo);
set.stream().forEach(cv -> {
System.out.println("属性:"+cv.getPropertyPath()+",属性值:"+cv.getInvalidValue()+",校验不通过,触发规则:"+cv.getMessage());
});
}
}
Validator初始化流程
Validation.buildDefaultValidatorFactory().getValidator();
逻辑可以拆分为如下两步:
Validation.buildDefaultValidatorFactory()
: 获取 ValidatorFactory$1.getValidator();
: 获取Validator
获取 ValidatorFactory
public static ValidatorFactory buildDefaultValidatorFactory() {
//1.1 byDefaultProvider() -- 返回 GenericBootstrapImpl();
//1.2 GenericBootstrapImpl.configure() --根据SPI 加载 javax.validation.spi.ValidationProvider
//1.3 ValidationProvider.buildValidatorFactory();
return byDefaultProvider().configure().buildValidatorFactory();
}
SPI ValidationProvider(SPI) -> hibernate.jar/META-INF/services/javax.validation.spi.ValidationProvider
HibernateValidator
public class HibernateValidator implements ValidationProvider<HibernateValidatorConfiguration> {
@Override
public HibernateValidatorConfiguration createSpecializedConfiguration(BootstrapState state) {
return HibernateValidatorConfiguration.class.cast( new ConfigurationImpl( this ) );
}
@Override
public Configuration<?> createGenericConfiguration(BootstrapState state) {
return new ConfigurationImpl( state );
}
//返回ValidatorFactoryImpl
@Override
public ValidatorFactory buildValidatorFactory(ConfigurationState configurationState) {
return new ValidatorFactoryImpl( configurationState );
}
}
最终获取的ValidatorFactory
: org.hibernate.validator.internal.engine.ValidatorFactoryImpl
获取Validator
最终Validator
类型: org.hibernate.validator.internal.engine.ValidatorFactoryImpl
demo1
constraints
@Data
public class UserInfo {
@javax.validation.constraints.NotNull
private Long id;
@javax.validation.constraints.NotEmpty
private String name;
@org.hibernate.validator.constraints.Range
private Integer age;
@javax.validation.constraints.Email
private String email;
private LocalDate birthdate;
}
test-case
public void test01(){
UserInfo userInfo = new UserInfo();
userInfo.setName("");
userInfo.setAge(120);
userInfo.setEmail("www.baidu.com");
ValidatorUtil.valid(userInfo);
}
执行结果

常见约束constraints
javax.validation.constraints
org.hibernate.validator.constraints
约束与Validator的绑定关系—ConstraintHelper
package org.hibernate.validator.internal.metadata.core;
public class ConstraintHelper {
public ConstraintHelper() {
Map<Class<? extends Annotation>, List<ConstraintValidatorDescriptor<?>>> tmpConstraints = new HashMap<>();
//为NotNull.class 绑定 NotNullValidator.class
putConstraint( tmpConstraints, NotNull.class, NotNullValidator.class );
putConstraint( tmpConstraints, Null.class, NullValidator.class );
putConstraint( tmpConstraints, Email.class, EmailValidator.class );
//......
}
}
分组校验
例如上面的UserInfo,
- id: 在新增时
不校验
,在编辑时校验id
- 其他属性,在新增或编辑时都校验
定义分组-class
public class ValidGroups {
public interface Add{
}
public interface Edit{
}
}
UserInfo
@Data
public class UserInfo {
@javax.validation.constraints.NotNull(groups = {
ValidGroups.Edit.class})
private Long id;
@javax.validation.constraints.NotEmpty(groups = {
ValidGroups.Add.class,ValidGroups.Edit.class})
private String name;
@org.hibernate.validator.constraints.Range(min = 18,message = "年龄小于{value},未成年",groups = {
ValidGroups.Add.class,ValidGroups.Edit.class})
private Integer age;
@javax.validation.constraints.Email(groups = {
ValidGroups.Add.class,ValidGroups.Edit.class})
private String email;
private LocalDate birthdate;
}
javax.validation.Validator
public interface Validator {
<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);
}
ValidatorUtil
public static List<String> valid(UserInfo userInfo,Class validGroup){
Set<ConstraintViolation<UserInfo>> set = validator.validate(userInfo,validGroup);
List<String> list = set.stream().map(cv ->
"属性:" + cv.getPropertyPath() + ",属性值:" + cv.getInvalidValue() + ",校验不通过,触发规则:" + cv.getMessage()
).collect(Collectors.toList());
list.forEach(System.out::println);
return list;
}
级联校验
UserInfo中存在grade年级信息,年级信息有自己的校验规则。
@Data
public class UserInfo {
//略...
@javax.validation.Valid //级联校验
private Grade grade;
}
@Data
public class Grade {
@NotEmpty
private String gradeNo;
@Range(min = 5,max = 40)
private int classNum;
}
在执行validator.validate(userInfo)
,若需要开启级联注解,则需要使用 @javax.validation.Valid注解。
自定义校验
定义校验规则constraints
@Target({
FIELD })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {
UserStatusValidator.class }) //指定validator
public @interface UserStatus {
String message() default "{user_status 必须为100/101/1002}";
Class<?>[] groups() default {
};
Class<? extends Payload>[] payload() default {
};
}
定义Validator
public class UserStatusValidator implements javax.validation.ConstraintValidator<UserStatus, Integer> {
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return value == 100 || value == 101 || value == 102;
}
}
failfast 校验
前文中的validator,会将所有的不合规校验信息全部收集完成之后,才返回。
如何设置,发现一个不合规信息,立即返回。
Validator failfastValidator = Validation.byProvider(HibernateValidator.class)
.configure().failFast(true).buildValidatorFactory().getValidator();
非javabean校验
UserInfoService
public class UserInfoService {
public String getByName(@NotNull String name) throws NoSuchMethodException {
// 校验...
Method method = this.getClass().getDeclaredMethod("getByName", String.class);
ValidatorUtil.validNotBean(this,method,new Object[]{
name});
return "name:" + name;
}
}
ExecutableValidator
public class ValidatorUtil {
private static Validator validator;
private static ExecutableValidator executableValidator;
static {
validator = Validation.buildDefaultValidatorFactory().getValidator();
executableValidator = validator.forExecutables();
}
public static <T> List<String> validNotBean(T object , Method method, Object[] params , Class<?> ... groups){
Set<ConstraintViolation<T>> set = executableValidator.validateParameters(object, method, params, groups);
List<String> list = set.stream().map(cv ->
"属性:" + cv.getPropertyPath() + ",属性值:" + cv.getInvalidValue() + ",校验不通过,触发规则:" + cv.getMessage()
).collect(Collectors.toList());
list.forEach(System.out::println);
return list;
}
}
Springboot 校验
Springboot环境搭建
maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.7</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
App主类
@SpringBootApplication
public class App {
public static void main( String[] args )
{
SpringApplication.run(App.class);
}
}
Controller
@RestController
public class UserInfoController {
@GetMapping("/getByName")
public String getByName(String name){
return "name:" + name;
}
@GetMapping("/addUser")
public String addUser(UserInfo userInfo){
List<String> list = ValidatorUtil.valid(userInfo);
if(list.size() > 0){
return "校验不成功";
}
return "添加成功";
}
}
Springmvc校验 - 声明式
@javax.validation.Valid
@GetMapping("/addUser2")
public String addUser2(@javax.validation.Valid UserInfo userInfo, BindingResult bindingResult){
if(bindingResult.hasErrors()){
bindingResult.getAllErrors().forEach(error ->{
System.out.println(error.getObjectName()+"::"+error.getDefaultMessage());
});
bindingResult.getFieldErrors().forEach(fieldError -> {
System.out.println(fieldError.getField()+":"+fieldError.getDefaultMessage()+",没有通过校验的值:"+fieldError.getRejectedValue() );
});
}
return "添加成功";
}
@javax.validation.Valid
无法指定分组。
@org.springframework.validation.annotation.Validated - 指定分组
@GetMapping("/addUser3")
public String addUser3(@org.springframework.validation.annotation.Validated(ValidGroups.Add.class) UserInfo userInfo, BindingResult bindingResult){
if(bindingResult.hasErrors()){
bindingResult.getAllErrors().forEach(error ->{
System.out.println(error.getObjectName()+"::"+error.getDefaultMessage());
});
bindingResult.getFieldErrors().forEach(fieldError -> {
System.out.println(fieldError.getField()+":"+fieldError.getDefaultMessage()+",没有通过校验的值:"+fieldError.getRejectedValue() );
});
}
return "添加成功";
}
统一异常处理
controller – 无BindingResult处理
@GetMapping("/addUser4")
public String addUser4(@org.springframework.validation.annotation.Validated(ValidGroups.Add.class) UserInfo userInfo){
return "添加成功";
}
若校验不通过,则会直接抛出异常。
@ExceptionHandler
在每个controller中添加@ExceptionHandler
注解的方法,来处理该controller的异常信息。
@ExceptionHandler(BindException.class)
public String handlerEx(BindException e){
List<FieldError> fieldErrors = e.getFieldErrors();
StringBuilder sb = new StringBuilder();
fieldErrors.forEach(fe ->
sb.append("属性:").append(fe.getField()).append("验证不通过,原因:").append(fe.getDefaultMessage()).append(";")
);
return sb.toString();
}
全局异常处理-ControllerAdvice
参见:SpringMVC-特性annotation - @ControllerAdvice
部分。
Valid 和 Validate 区别
- Validate 支持分组
- Validate支持方法参数上的自动校验。
@GetMapping("/getByName")
//在方法上或者类上增加 @Validated :@Null方有效果
public String getByName(@NotNull String name){
return "name:" + name;
}
此时的@Null
校验是不起作用的,需要在该方法或者类上增加@Validate
注解。
当在类上增加此注解时,若校验不通过,抛出ConstraintViolationException异常.
@ExceptionHandler(ConstraintViolationException.class)
public String handlerEx2(ConstraintViolationException e){
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
StringBuilder sb = new StringBuilder();
//TODO ....
return sb.toString();
}
边栏推荐
- redis数据结构分析
- Deep learning - (2) several common loss functions
- RT thread heap size Setting
- 【Verilog基础】十进制负数的八进制、十六进制表示
- 备战数学建模34-BP神经网络预测2
- Observation cloud reached in-depth cooperation with tdengine to optimize enterprise cloud experience
- 微信表情符号写入判决书,你发的OK、炸弹都可能成为“呈堂证供”
- Multi terminal collaboration of Huawei accounts to create a better internet life
- Etcd tutorial - Chapter 8 compact, watch, and lease APIs for etcd
- Good partner for cloud skill improvement, senior brother cloud of Amazon officially opened today
猜你喜欢
AVIC UAV technology innovation board is listed: the fist product with a market value of 38.5 billion is pterodactyl UAV
The 25th anniversary of Hong Kong's return to China the Hong Kong Palace Museum officially opened as a new cultural landmark
Multi terminal collaboration of Huawei accounts to create a better internet life
Mathematical modeling for war preparation 35 time series prediction model
商单视频播放超2000万!农院改造为何屡被催更?
MC Instruction Decoder
Data security compliance has brought new problems to the risk control team
List announced - outstanding intellectual property service team in China in 2021
differential analysis between different groups nichenet for silicosis成功运行!
Rong Lianyun launched rphone based on Tongxin UOS to create a new ecology of localization contact center
随机推荐
商单视频播放超2000万!农院改造为何屡被催更?
TCP Socket与TCP 连接
List announced - outstanding intellectual property service team in China in 2021
Restartprocessifvisible process
[activity registration] it's your turn to explore the yuan universe! I will be waiting for you in Shenzhen on July 2!
HMS core audio editing service 3D audio technology helps create an immersive auditory feast
Wechat emoticons are written into the judgment, and the OK and bomb you send may become "testimony in court"
腾讯二面:@Bean 与 @Component 用在同一个类上,会怎么样?
Raft介绍
dart:字符串replace相关的方法
OpenCV中LineTypes各枚举值(LINE_4 、LINE_8 、LINE_AA )的含义
Niuke: how many different binary search trees are there
Lambda expression_ Stream stream_ File class
9:第三章:电商工程分析:4:【通用模块】;(待写……)
微信表情符号写入判决书,你发的OK、炸弹都可能成为“呈堂证供”
Etcd教程 — 第九章 Etcd之实现分布式锁
go-micro教程 — 第一章 快速入门
【Verilog基础】十进制负数的八进制、十六进制表示
数据挖掘知识点整理(期末复习版)
【微信小程序】常用组件基本使用(view/scroll-view/swiper、text/rich-text、button/image)