使用场景

1.Spring MVC 传入参数校验 结合@ControllerAdvice || @RestControllerAdvice
2.结合@ConfigurationProperties 从yml || properties中读取配置构造bean时候使用
3.结合@Value可以对@Value值做校验
4.实际业务中Service层也有公司用来校验

常见组合用法

1.如果使用@ConfigurationProperties || @Value对配置做校验,常搭配META-INF/additional-spring-configuration-metadata-json ,META-INF/spring-configuration-metadata.json 一起使用
2.在SpringMVC中校验常用DTO对入参对象校验,会涉及到@Validation(value=xxx.class) 用分组来对不同业务操作校验

@Validated @Valid区别

1.@Valid用户嵌套校验,其余如果不深究使用@Validated就可以了
2.Validated是org.springframework的annotation,@Valid 注解是javax.validation的注解.
3.@Validated:提供分组功能,可以在参数验证时,根据不同的分组采用不同的验证机制,@Valid:没有分组功能
4.@Validated:用在类型、方法和方法参数上。但不能用于成员属性(field),@Valid:可以用在方法、构造函数、方法参数和成员属性(field)上

ConfigurationProperties Validation

@ConfigurationProperties(prefix="acme")
@Validated
public class AcmeProperties {

@NotNull
private InetAddress remoteAddress;

// ... getters and setters

}

详情可查看​​@ConfigurationProperties Validation​

Service Validation

@Service
@Validated
public class MyBean {

public Archive findByCodeAndAuthor(@Size(min = 8, max = 10) String code,
Author author) {
...
}

}

详情可查看 ​​@Service Validation​

快速记忆使用方式

Bean类加@Validation,需要校验的对象前加@Validation,对象里的嵌套校验加@Valid

@Validated 原理

1.基于Spring Boot的AutoConfiguration: org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration
2.创建MethodValidationPostProcessor的Bean对象

MethodValidationPostProcessor 源码分析

Spring boot - @Validated @Valid 优雅校验_spring


实现InitializingBean接口

Spring boot - @Validated @Valid 优雅校验_java_02


Spring boot - @Validated @Valid 优雅校验_java_03

Spring boot - @Validated @Valid 优雅校验_spring_04

由其中可看出仅对带有@Validated的Bean对象做PointCut处理

验证框架

自定义校验框架

Group Class

public interface GroupClasses {

interface Template {

}

interface Add {

}

interface Query {

}

interface Update {

}

interface Delete {

}

}

CheckAtLeastOneNotBlankValidator

在类上添加注解

e.g. 在类上添加

@CheckAtLeastOneNotBlank(message = "image || imageUrl 参数必填一项", fieldNames = {"image", "imageUrl"})

Annotation -> CheckAtLeastOneNotBlank

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

/**
* @author <a href="mailto:boommanpro@gmail.com">boommanpro</a>
*/

@Target({TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = CheckAtLeastOneNotBlankValidator.class)
@Documented
public @interface CheckAtLeastOneNotBlank {

String message() default "";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

String[] fieldNames();

}

CheckAtLestOneNotBlankValidator

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.IntrospectionUtils;
import org.springframework.stereotype.Component;

/**
* @author <a href="mailto:boommanpro@gmail.com">boommanpro</a>
*/
@Slf4j
@Component
class CheckAtLeastOneNotBlankValidator implements ConstraintValidator<CheckAtLeastOneNotBlank, Object> {

private String[] fieldNames;

@Override
public void initialize(CheckAtLeastOneNotBlank constraintAnnotation) {
this.fieldNames = constraintAnnotation.fieldNames();
}

@Override
public boolean isValid(Object object, ConstraintValidatorContext constraintContext) {
if (object == null) {
return true;
}
for (String fieldName : fieldNames) {
String property = (String) IntrospectionUtils.getProperty(object, fieldName);
if (StringUtils.isNoneBlank(property)) {
return true;
}
}
return false;
}

}

ValidateMobile

在字段上添加@ValidateMobile

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.RetentionPolicy.RUNTIME;

// 约束注解应用的目标元素类型(TYPE ,METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER等)
@Target({TYPE, METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
// 约束注解应用的时机
@Retention(RUNTIME)
@Documented
// 与约束注解关联的验证器
@Constraint(validatedBy = { MobileValidator.class})
public @interface ValidateMobile {

//是否校验
boolean required() default true;
// 约束注解验证时的输出消息
String message() default "手机号码格式不正确";
// 约束注解在验证时所属的组别
Class<?>[] groups() default { };
// 约束注解的有效负载
Class<? extends Payload>[] payload() default { };

}

MobileValidator

import cn.boommanpro.util.MobileUtil;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class MobileValidator implements ConstraintValidator<ValidateMobile,String> {


private boolean required = false;

@Override
public void initialize(ValidateMobile constraintAnnotation) {
required = constraintAnnotation.required();
}


@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (required) {
return MobileUtil.isMobile(value);
}
return true;

}


}

MobileUtil 判断手机号码的工具类

public class MobileUtil {

/**
* 判断字符串是否符合手机号码格式
* 移动号段: 134,135,136,137,138,139,147,150,151,152,157,158,159,170,178,182,183,184,187,188
* 联通号段: 130,131,132,145,155,156,170,171,175,176,185,186
* 电信号段: 133,149,153,170,173,177,180,181,189
*
* @return 待检测的字符串
*/
public static boolean isMobile(String mobileNums) {
// "[1]"代表第1位为数字1,"[358]"代表第二位可以为3、5、8中的一个,"\\d{9}"代表后面是可以是0~9的数字,有9位。
String telRegex = "^(0|86|17951)?(13[0-9]|15[012356789]|16[123456789]|17[0135678]|199|18[0-9]|14[579])[0-9|*]{4}[0-9]{4}$";

if (StringUtils.isNullOrEmpty(mobileNums)) {
return false;
} else {
return mobileNums.matches(telRegex);
}

}

public static boolean isNotMobile(String mobileNums) {
return !isMobile(mobileNums);
}
}