优雅的参数验证——javax.validation
1.相关环境
1.基本环境
jdk 1.8
maven 3.2.5
springboot 2.1.5.RELEASE
2.相关依赖
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.0.6</version>
</dependency>
2.使用
1.基本使用——内置注解
例如:在我们接受前台的参数时候,可以使用javax.validation进行校验,只需要打上对应的注解就可以完成基本的验证
public class TestDto {
@NotBlank(message = "{user.register.username.not-blank}")
private String userName;
}
拓展:将对应的非法内容统一放置到对应的ValidationMessages.properties,
位置为src/main/resources下
注意:中文乱码 需要将properties的编码设置为UTF-8,在yml设置
spring:
messages:
encoding: UTF-8
例如
user.register.username.not-blank=用户名不可为空
2.拓展使用---自定义注解
我们需要根据自己的业务拓展不同的validtor,简化臃肿的代码
例如:判断两个字符串是否相同
1.自定义注解
定义验证所用的注解
import life.xp.jinglongeat.common.validator.impl.EqualFiledValidator;
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.*;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @Author xp
* @Description 自定义验证字符串是否相同----用于校验前台内容的一致性
* @Date 14:21 2020/2/15
* @Param
* @return
**/
@Target({TYPE, METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EqualFiledValidator.class})
public @interface EqualField {
/**源字段*/
String srcField() default "";
// dstField = "confirmPassword",
/**目标字段*/
String dstField() default "";
// message = "{password.equal-field}"
String message() default "the two fields must be equal";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2.对应实现
使用了反射去拿到对应的字段进行校验
import life.xp.jinglongeat.common.validator.EqualField;
import org.springframework.util.ReflectionUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Field;
/**
* @ClassName: EqualFiledValidator
* @Description: 自定义注解实现
* @author: XP
* @data: 2020/2/15
*/
public class EqualFiledValidator implements ConstraintValidator<EqualField, Object> {
/**源字段*/
private String srcField;
/**目标字段*/
private String dstField;
@Override
public void initialize(EqualField constraintAnnotation) {
this.srcField=constraintAnnotation.srcField();
this.dstField=constraintAnnotation.dstField();
}
@Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
//1.拿到当前对象--dto
Class<?> aClass = o.getClass();
//拿到对应的字段
Field srcField = ReflectionUtils.findField(aClass, this.srcField);
Field dstField = ReflectionUtils.findField(aClass, this.dstField);
try {
srcField.setAccessible(true);
dstField.setAccessible(true);
//方法返回指定对象上由此Field表示的字段的值
String src=(String)srcField.get(o);
String dst=(String)dstField.get(o);
//拿到对象的值 进行相同判断
if(src.equals(dst)){
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
3.使用
/**
* @ClassName: TestDto
* @Description: 测试validator
* @author: XP
* @data: 2020/2/15
*/
@NoArgsConstructor
@Getter
@Setter
@EqualField(srcField = "password", dstField = "confirmPassword", message = "{password.equal-field}")
public class TestDto {
@NotBlank(message = "{user.register.username.not-blank}")
private String userName;
@NotBlank(message = "{password.new-password.not-blank}")
private String password;
@NotBlank(message = "{password.confirm-password.not-blank}")
private String confirmPassword;
}
3.原理
例如分析@NotBlank的源码
/*
* Bean Validation API
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package javax.validation.constraints;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.NotBlank.List;
/**
* The annotated element must not be {@code null} and must contain at least one
* non-whitespace character. Accepts {@code CharSequence}.
*
* @author Hardy Ferentschik
* @since 2.0
*
* @see Character#isWhitespace(char)
*/
@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
public @interface NotBlank {
//内置相关验证说明
String message() default "{javax.validation.constraints.NotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
/**
* Defines several {@code @NotBlank} constraints on the same element.
*
* @see NotBlank
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface List {
NotBlank[] value();
}
}
个人理解:在javax.validation.constraints包里面并没有实现相关的validator,提供了ValidatorFactory接口
而在相关相关的org.hibernate.validator提供了实现,大致上是:
HibernateValidatorFactory实现ValidatorFactory接口 对对应的validtor做了实现
例如上述的@NotBlank
通过打断点的方式发现的确如此
4.总结
实现原理:注解+反射的方式实现对验证的扩展 源码分析是通过工厂的方式让其他来做实现
祝君好梦!