一、基本使用
1.maven引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2.controller中使用
基本分为三步
- 为请求方法中的待验证参数添加@Valid注解
- 创建待验证对象,并为待验证属性添加验证注解
- 创建全局异常处理器,捕获并统一处理验证不通过时抛出的BindException异常
@RestController
@RequestMapping("/test")
public class TestController {
//1.方法参数只能是对象,待验证的请求参数为对象属性
//2.必须加上@Valid注解,否则springmvc无法进行验证
@RequestMapping(value = "/req.json")
public Object test(@Valid TestRequest req, HttpServletRequest request) {
return null;
}
}
public class TestRequest{
//@NotBlank 注解为验证框架提供的默认注解之一,方便程序员使用
//message为错误提示
@NotBlank(message = "req不能为空")
private String req;
}
@RestControllerAdvice
public class ExceptionHandle {
//验证不通过会抛出BindException,可以在这里对异常进行统一处理,比如返回参数错误
@ExceptionHandler
public Object handleException(HttpServletRequest request, Exception ex) {
Throwable rootCause = ExceptionUtils.getRootCause(ex);
ObjectError objectError = null;
if (ex instanceof BindException) {
objectError = ((BindException) ex).getBindingResult().getAllErrors().get(0);
}
if (ex instanceof MethodArgumentNotValidException) { objectError = ((MethodArgumentNotValidException) ex).getBindingResult().getAllErrors() .get(0);
}
if (objectError != null) {
String msg = localeMessage.getMessage(objectError.getDefaultMessage(),
objectError.getDefaultMessage(), RequestUtils.currentLocale(request));
}
}
}
3.常用注解
注解名称 | 说明 |
---|---|
NotBlank | 值必须不为空(空白字符不算空,空格等) |
AssertFalse | 值必须为false |
AssertTrue | 值必须为true |
DecimalMax | 值必须为数字,且不能超过value属性指定的最大值,inclusive属性决定是否可包含value值,不能用于double和float字段 |
DecimalMin | 值必须为数字,且不能小于value属性指定的最大值,inclusive属性决定是否可包含value值,不能用于double和float字段 |
Digits | 值必须为数字,且不能integer和fraction属性指定的整数和小数范围 |
值必须为email格式 | |
Future | 值必须为日期,且必须大于当前时间 |
FutureOrPresent | 值必须为日期,且必须大于等于当前时间 |
Past | 值必须为日期,且必须小于当前时间 |
PastOrPresent | 值必须为日期,且必须小于等于当前时间 |
Max | 值必须为数字,且不能超过value属性指定的最大值,inclusive属性决定是否可包含value值,不能用于double和float字段 |
Min | 值必须为数字,且不能小于value属性指定的最大值,inclusive属性决定是否可包含value值,不能用于double和float字段 |
Negative | 值必须为负数 |
NegativeOrZero | 值必须为负数或0 |
Positive | 值必须为正数 |
PositiveOrZero | 值必须为正数或0 |
NotEmpty | 值必须不为空,可以支持集合,map,和数组 |
NotNull | 值必须为非null |
Null | 值必须为null |
Pattern | 值必须匹配regexp属性指定的正则表达式 |
Size | 值必须在min和max属性值指定的范围内(包含),字段可以为map,array,集合,值为其元素数 |
一、工作原理
1.spring-boot引入
spring-boot-autoconfigure-xxx.jar中org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration 进行自动配置
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ExecutableValidator.class)
@ConditionalOnResource(resources =
"classpath:META-INF/services/javax.validation.spi.ValidationProvider")
@Import(PrimaryDefaultValidatorPostProcessor.class)
public class ValidationAutoConfiguration {
//LocalValidatorFactoryBean 验证器工厂,其通过services发现机制来寻找第三方实现的验证器(javax.validation.spi.ValidationProvider)。这里使用的是hibernate的实现
//springmvc主要使用此验证器进行验证
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnMissingBean(Validator.class)
public static LocalValidatorFactoryBean defaultValidator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
return factoryBean;
}
//动态代理的方式拦截指定方法,验证方法参数,参数可以基本类型,可以弥补springmvc验证方式的不足(springmvc不能校验请求方法中的基本参数)
//需要在类上标注@Validated注解
//验证不通过会抛出ConstraintViolationException
@Bean
@ConditionalOnMissingBean
public static MethodValidationPostProcessor
methodValidationPostProcessor( Environment environment, @Lazy Validator validator) {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
boolean proxyTargetClass = environment .getProperty("spring.aop.proxy-target-class", Boolean.class, true);
processor.setProxyTargetClass(proxyTargetClass);
processor.setValidator(validator);
return processor;
}
}
2.spring-mvc中使用
在请求参数解析时会进行参数验证。springmvc对象参数解析器org.springframework.web.method.annotation.ModelAttributeMethodProcessor
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @NullableModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//......
if (bindingResult == null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
//调用验证器进行验证
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
//验证失败之后在此抛出BindException异常
throw new BindException(binder.getBindingResult());
}
}
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(),
parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
//......
return attribute;
}
//在验证之前会先调用此方法判断参数需不需要进行验证,只有在参数上加上Validated注解或者其他的以Valid开头的注解才会进行验证
private Object[] determineValidationHints(Annotation ann) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
if (hints == null) {
return new Object[0];
}
return (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
}
return null;
}
}
3.hibernate-validator 实现
- 所有的注解必须继承Constraint注解,如@NotBlank。validatedBy指定了验证器,若果没有指定具体验证器,会从默认的验证器中查找
- 当把待验证对象传给框架验证时,会去查询对象及其属性是否带有有效的注解,如果有则进行验证
- org.hibernate.validator.internal.metadata.core.ConstraintHelper类中初始化了所有的默认验证器,如@NotBlank验证器,并提供验证器的缓存功能;其isConstraintAnnotation方法用于判断方法或字段是否需要验证;getDefaultValidatorDescriptors方法为获取验证器的流程。
public class ConstraintHelper {
public ConstraintHelper() {
//.....
//初始化默认验证器
putConstraint( tmpConstraints, NotBlank.class,
NotBlankValidator.class );
//.....
}
//判断是否是有效的验证注解
public boolean isConstraintAnnotation(Class<? extends
Annotation> annotationType) {
if ( isBuiltinConstraint( annotationType ) ) {
return true;
}
if ( annotationType.getAnnotation( Constraint.class ) == null ) {
return false;
}
return externalConstraints.computeIfAbsent( annotationType, a -> {
assertMessageParameterExists( a );
assertGroupsParameterExists( a );
assertPayloadParameterExists( a );
assertValidationAppliesToParameterSetUpCorrectly( a );
assertNoParameterStartsWithValid( a );
return Boolean.TRUE;
} );
}
//获取验证,首先从默认验证器中查找,如果没有找到则取Constraint注解的validatedBy属性值(提供了自定义验证器的能力)
private <A extends Annotation> List<ConstraintValidatorDescriptor<A>>
getDefaultValidatorDescriptors(Class<A> annotationType) {
final List<ConstraintValidatorDescriptor<A>> builtInValidators = (List<ConstraintValidatorDescriptor<A>>) builtinConstraints.get( annotationType );
if ( builtInValidators != null ) {
return builtInValidators;
}
Class<? extends ConstraintValidator<A, ?>>[] validatedBy = (Class<? extends ConstraintValidator<A, ?>>[]) annotationType .getAnnotation( Constraint.class ) .validatedBy();
return Stream.of( validatedBy ) .map( c -> ConstraintValidatorDescriptor.forClass( c, annotationType ) ) .collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) );
}
}
4.自定义验证注解
基本步骤
- 创建自定义注解并继承Constraint注解
- 创建验证器对象实现ConstraintValidator接口
- 设置Constraint注解的validatedBy属性指向ConstraintValidator实现对象
- 现在可以像使用@NotBlank一样使用自定义注解
//自定义注解
@Constraint(validatedBy = TestValidator.class)
@Target({
ElementType.METHOD, ElementType.FIELD
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestValidate {
String message() default "{javax.validation.constraints.NotBlank.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
//自定义验证器
public class TestValidator implements ConstraintValidator<TestValidate, CharSequence> {
@Override
public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
if ( charSequence == null ) {
return false;
}
return charSequence.toString().trim().length() > 0;
}
}