spring-boot-starter-validation 参照文档
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
常用注解
NotNull
NotBlank
Max
Min
Size
大部分注解
NotNull
带注解的字段不能为null
NotEmpty
带注解的字段不能为null也不能为空
支持
Map
、CharSequence
、Collection
、Array
AssertFalse
带注解的字段必须为
false
AssertTrue
带注解的字段必须为
true
DecimalMax
带注解的元素必须是一个数字,其值必须小于或等于指定的最大值
支持
BigDecimal
、BigInteger
、CharSequence
,
byte
、short
、int
、long
和它们各自的包装类型
DecimalMin
带注解的元素必须是一个数字,其值必须大于或等于指定的最小值
支持
BigDecimal
、BigInteger
、CharSequence
,
byte
、short
、int
、long
和它们各自的包装类型`
Digits
带注解的元素必须是可接受范围内的数字
支持
BigDecimal
、BigInteger
、CharSequence
,
byte
、short
、int
、long
和它们各自的包装类型`
Email
带注解的字符串必须是格式正确的电子邮件地址
null元素被认为是有效的
Future
带注解的元素必须是未来的瞬间、日期或时间
支持的类型有:
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
Max
带注解的元素必须是一个数字,其值必须小于或等于指定的最大值。
支持的类型有:
BigDecimal
BigInteger
byte
、short
、int
、long
和它们各自的包装器
请注意,由于舍入错误,不支持double
和float
(某些提供程序可能会提供一些近似支持)。
null
元素被认为是有效的。
Min
带注解的元素必须是一个数字,其值必须大于或等于指定的最小值。
支持类型以及注意事项见第10条:
Max
Negative
带注解的元素必须是严格的负数(即,0 被视为无效值)
支持的类型有:
BigDecimal
BigInteger
byte
、short
、int
、long
,float
,double
和它们各自的包装器
null
元素被认为是有效的。
NegativeOrZero
带注解的元素必须是负数或 0
支持类型见第12条:
Negative
NotBlank
带注解的元素不能为null并且必须至少包含一个非空白字符。 仅支持
CharSequence
Null
带注解的元素必须为null 。 接受任何类型
Past
带注解的元素必须是过去的瞬间、日期或时间
支持的类型见第9条:
Future
Pattern
带注解的
CharSequence
必须匹配指定的正则表达式。 正则表达式遵循 Java 正则表达式约定
null
元素被认为是有效的。
Positive
带注解的元素必须是严格的正数(即 0 被视为无效值)。
支持的类型见第12条:Negative
null
元素被认为是有效的。
PositiveOrZero
带注解的元素必须是正数或 0。
支持的类型见第12条:Negative
null
元素被认为是有效的。
Size
带注解的元素大小必须在指定边界(包括)之间。
支持的类型有:
CharSequence
(评估字符序列的长度)
Collection
(评估集合大小)
Map
(评估Map大小)
Array
(评估数组长度)
null
元素被认为是有效的。
ScriptAssert
一个类级别的约束,它根据带注释的元素评估脚本表达式。 此约束可用于实现依赖于带注释元素的多个属性的验证例程。
脚本表达式可以用任何脚本或表达式语言编写,在类路径中可以找到与JSR 223
(“Java TM平台脚本”)兼容的引擎。 以下清单显示了一个使用JDK
附带的JavaScript
引擎的示例:将真正的表达式语言与较短的对象别名结合使用可以实现非常紧凑的表达式:
注:
javascript
就是java
可直接调用类中方法:
公共方法:
_this.methodName(params)
静态方法:
com.demo.utils.ValidUtils.LocalDateTimeValid(params)
方法返回值需要布尔类型,错误消息需要指定
message
属性
@ScriptAssert(lang = "javascript", script = "_this.startDate.before(_this.endDate)")
public class CalendarEvent {
private Date startDate;
private Date endDate;
//...
}
@ScriptAssert(lang = "jexl", script = "_.startDate > _.endDate", alias = "_")
public class CalendarEvent {
private Date startDate;
private Date endDate;
//...
}
Length
验证字符串是否介于
min
和max
之间
Range
带注解的元素必须在适当的范围内。 应用于数值或数值的字符串表示
URL
验证带注解的字符串是 URL
参数protocol 、 host和port与 URL 的相应部分匹配。 并且可以使用
regexp
和flags指定额外的正则表达式以进一步限制匹配条件。
注意:默认情况下,此约束的约束验证器使用java.net.URL
构造函数来验证字符串。 这意味着匹配的协议处理程序需要可用
CreditCardNumber
带注解的元素必须代表一个有效的信用卡号。 这是
Luhn
算法实现,旨在检查用户错误,而不是信用卡有效性
错误消息模板示例:
例如
Max
、Min
、Range
@Max(value = 100, message = "最大不超过{value}")
@Min(value = 0, message = "最小不低于{value}")
@Range(min = 0, max = 100, message = "数值介于{min}~{max}之间")
方法参数校验使用示例:
@Validated
public class Test {
public void test(@Min(value = 0, message = "最小不低于{value}") Integer age){}
}
public class Test {
public void test(@RequestBody @Valid User user){}
}
嵌套校验示例:
@Valid
private User user;
分组校验示例:
public interface CreateGroup {}
public interface UpdateGroup {}
public class TestDto {
@Max(value = 100, groups = {CreateGroup.class}, message = "最大不超过{value}")
@Min(value = 0, groups = {UpdateGroup.class}, message = "最小不低于{value}")
private Integer age;
}
public class Test {
public void test1(@RequestBody @Validated({CreateGroup.class}) TestDto dto){}
public void test2(@RequestBody @Validated({UpdateGroup.class}) TestDto dto){}
}
// 注解Valid与Validated可以混用
// Validated是Valid的变体
// Valid不支持分组校验
手动触发校验
//注入javax.validation.Validator
@Autowired
private Validator validator;
public void valid(TestDto dto, Class<?> groupClass) {
Set<ConstraintViolation<TestDto>> validateSet = validator.validate(dto, groupClass);
if(validateSet.isEmpty()) {
// 校验通过
return;
}
// 校验失败
for(ConstraintViolation<TestDto> validate: validateSet) {
System.out.println(validate);
}
}
Spring boot
配置校验快速失败模式
// 快速失败即校验到一个不符合条件就结束了
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失败模式
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
RequestBody
校验实现原理
可以看看org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
。
其中方法resolveArgument
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 将数据封装到参数对象中
parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
// 参数校验
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
// 如果有Validated注解或者Valid开头的注解则开始校验
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
binder.validate(validationHints);
break;
}
}
}
// WebDataBinder.validate实现
public void validate(Object target, Errors errors, Object... validationHints) {
if (this.targetValidator != null) {
processConstraintViolations(
// 调用Hibernate Validator执行真正的校验
this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);
}
}
方法参数校验实现原理
实现原理其实就是AOP
,这种方式实际上可以作用与任何spring bean
可以看看org.springframework.validation.beanvalidation.MethodValidationPostProcessor
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
implements InitializingBean {
private Class<? extends Annotation> validatedAnnotationType = Validated.class;
@Override
public void afterPropertiesSet() {
// 为所有`@Validated`标注的Bean创建切面
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
// 创建Advisor进行增强
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
}
/**
* Create AOP advice for method validation purposes, to be applied
* with a pointcut for the specified 'validated' annotation.
* @param validator the JSR-303 Validator to delegate to
* @return the interceptor to use (typically, but not necessarily,
* a {@link MethodValidationInterceptor} or subclass thereof)
* @since 4.2
*/
protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
}
}
public class MethodValidationInterceptor implements MethodInterceptor {
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Avoid Validator invocation on FactoryBean.getObjectType/isSingleton
if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
return invocation.proceed();
}
// 分组
Class<?>[] groups = determineValidationGroups(invocation);
// Standard Bean Validation 1.1 API
ExecutableValidator execVal = this.validator.forExecutables();
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<Object>> result;
Object target = invocation.getThis();
Assert.state(target != null, "Target must not be null");
try {
// 方法入参校验
result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
}
catch (IllegalArgumentException ex) {
// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
// Let's try to find the bridged method on the implementation class...
methodToValidate = BridgeMethodResolver.findBridgedMethod(
ClassUtils.getMostSpecificMethod(invocation.getMethod(), target.getClass()));
result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
}
// 校验不通过抛出异常
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
Object returnValue = invocation.proceed();
// 返回值的校验
result = execVal.validateReturnValue(target, methodToValidate, returnValue, groups);
// 校验不通过抛出异常
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
return returnValue;
}
private boolean isFactoryBeanMetadataMethod(Method method) {
Class<?> clazz = method.getDeclaringClass();
// Call from interface-based proxy handle, allowing for an efficient check?
if (clazz.isInterface()) {
return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) &&
!method.getName().equals("getObject"));
}
// Call from CGLIB proxy handle, potentially implementing a FactoryBean method?
Class<?> factoryBeanType = null;
if (SmartFactoryBean.class.isAssignableFrom(clazz)) {
factoryBeanType = SmartFactoryBean.class;
}
else if (FactoryBean.class.isAssignableFrom(clazz)) {
factoryBeanType = FactoryBean.class;
}
return (factoryBeanType != null && !method.getName().equals("getObject") &&
ClassUtils.hasMethod(factoryBeanType, method));
}
/**
* Determine the validation groups to validate against for the given method invocation.
* <p>Default are the validation groups as specified in the {@link Validated} annotation
* on the containing target class of the method.
* @param invocation the current MethodInvocation
* @return the applicable validation groups as a Class array
*/
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class);
if (validatedAnn == null) {
Object target = invocation.getThis();
Assert.state(target != null, "Target must not be null");
validatedAnn = AnnotationUtils.findAnnotation(target.getClass(), Validated.class);
}
return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
}
}
不管是requestBody
参数校验还是方法参数校验,最终都是调用的Hibernate Validator
。
闲谈
上述都是以注解的形式对入参进行校验,我们也可以自己书写校验的方法然同样的可以达到目的。
对于一些业务逻辑本文章介绍的校验可能并不太适用。
个人建议是使用注解的形式来校验空参即可,其余的校验我们自己书写校验的方法在业务方法开始之前调用即可。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/195207.html