《乐之者java-validator》学习笔记
JAVAEE的规范,无具体实现
。具体的实现
。假设有登录场景,登录参数为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....
}
java EE 规范只有API,没有实现
。 具体的实现,由各厂商实现。例如:
javax.sql.*
javax.servlet
javax.jmx
并不是仅有这些,上述为javaee常见的一些规范。
对于一些上面没有的,则需要手动引入api依赖。
Java Community Process
oracle收购sun之后,oralce开始实行java商业授权。迫于java开源社区的压力,oracle将javax规范
捐献给了eclipse
.
但是不允许使用javax.*
,所以才有了现在的jakarta。
所以以后会出现:jakarta.sq.....l
不要跟
apache.jakarta
混为一谈。
<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>
@Data
public class UserInfo {
private Long id;
private String name;
private Integer age;
private String email;
private LocalDate birthdate;
}
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());
});
}
}
Validation.buildDefaultValidatorFactory().getValidator();
逻辑可以拆分为如下两步:
Validation.buildDefaultValidatorFactory()
: 获取 ValidatorFactory$1.getValidator();
: 获取Validator 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
类型: org.hibernate.validator.internal.engine.ValidatorFactoryImpl
@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;
}
public void test01(){
UserInfo userInfo = new UserInfo();
userInfo.setName("");
userInfo.setAge(120);
userInfo.setEmail("www.baidu.com");
ValidatorUtil.valid(userInfo);
}
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
public class ValidGroups {
public interface Add{}
public interface Edit{}
}
@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;
}
public interface Validator {
<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);
}
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注解。
@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 { };
}
public class UserStatusValidator implements javax.validation.ConstraintValidator<UserStatus, Integer> {
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return value == 100 || value == 101 || value == 102;
}
}
前文中的validator,会将所有的不合规校验信息全部收集完成之后,才返回。
如何设置,发现一个不合规信息,立即返回。
Validator failfastValidator = Validation.byProvider(HibernateValidator.class)
.configure().failFast(true).buildValidatorFactory().getValidator();
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;
}
}
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;
}
}
<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>
@SpringBootApplication
public class App {
public static void main( String[] args )
{
SpringApplication.run(App.class);
}
}
@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 "添加成功";
}
}
@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
无法指定分组。
@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 "添加成功";
}
@GetMapping("/addUser4")
public String addUser4(@org.springframework.validation.annotation.Validated(ValidGroups.Add.class) UserInfo userInfo){
return "添加成功";
}
若校验不通过,则会直接抛出异常。
在每个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();
}
参见:SpringMVC-特性annotation - @ControllerAdvice
部分。
@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();
}