参数校验通常是OpenApi必做的操作,其会对不合法的输入做统一的校验以防止恶意的请求。本文则对参数校验这方面作下简单的分析
spring.factories
读者应该对此文件加以深刻的印象,很多springboot整合第三方插件的方式均是从此配置文件去读取的,本文关注下检验方面的东西。在相应的文件搜索validation关键字,最终定位至ValidationAutoConfiguration类,笔者这就针对此类作主要的分析
ValidationAutoConfiguration
优先看下其头上的注解
@Configuration@ConditionalOnClass(ExecutableValidator.class)@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")@Import(PrimaryDefaultValidatorPostProcessor.class)
使此类成功被注册的条件有两个,第一是当前环境下存在ExecutableValidator类,第二是当前类环境存在META-INF/services/javax.validation.spi.ValidationProvider文件。
通过查看maven依赖得知,其实springboot在引入starter-web板块便引入了hibernate-validator包,此包便满足了上述的两个要求。
笔者发现其也引入了PrimaryDefaultValidatorPostProcessor类,主要是判断当前的bean工厂是否已经包含了LocalValidatorFactoryBean和Validator对象,不影响大局。即使没有配置,下述的代码也是会注册的
@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;
} @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;
}通过查阅代码得知,使用注解式的校验方式是通过添加@Validated注解来实现的,但是其作用于参数上还是类上是有不同的操作逻辑的。笔者将之区分开,方便后续查阅。先附上@Validated注解源码
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Validated { /**
* Specify one or more validation groups to apply to the validation step
* kicked off by this annotation.
* <p>JSR-303 defines validation groups as custom annotations which an application declares
* for the sole purpose of using them as type-safe group arguments, as implemented in
* {@link org.springframework.validation.beanvalidation.SpringValidatorAdapter}.
* <p>Other {@link org.springframework.validation.SmartValidator} implementations may
* support class arguments in other ways as well.
*/
Class<?>[] value() default {};
}类级别的校验
即@Validated作用于类上,其相关的处理逻辑便是由MethodValidationPostProcessor来实现的,笔者稍微看下关键源码方法afterPropertiesSet()
@Override
public void afterPropertiesSet() { // 查找对应的类以及祖先类上是否含有@Validated注解
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true); // 创建MethodValidationInterceptor处理类来处理具体的逻辑
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
}上述的配置表明只要某个类上使用了@Validated注解,其相应的方法就会被校验相关的参数。笔者紧接着看下MethodValidationInterceptor#invoke()方法
@Override
@SuppressWarnings("unchecked") public Object invoke(MethodInvocation invocation) throws Throwable { // 读取相应方法上的@Validated的value属性,为空也是没问题的
Class<?>[] groups = determineValidationGroups(invocation); // Standard Bean Validation 1.1 API
ExecutableValidator execVal = this.validator.forExecutables();
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<Object>> result; try { // ①校验参数
result = execVal.validateParameters(
invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
} catch (IllegalArgumentException ex) { // ②校验对应的桥接方法(兼容jdk1.5+后的泛型用法)的参数
methodToValidate = BridgeMethodResolver.findBridgedMethod(
ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass()));
result = execVal.validateParameters(
invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
} if (!result.isEmpty()) { throw new ConstraintViolationException(result);
} // ③校验对应的返回值
Object returnValue = invocation.proceed();
result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups); if (!result.isEmpty()) { throw new ConstraintViolationException(result);
} return returnValue;
}只要类上使用了@Validated注解,则其下的所有方法都会被校验。
检验规则如下:参数和返回值都会被校验,只要某一个没有通过,则会抛出ConstraintViolationException异常以示警告。
具体的参数校验属于hibernate-validator的范畴了,感兴趣的读者可自行分析~
参数级别的校验
即@Validated注解作用于方法的参数上,其有关的校验则是被springmvc的参数校验器处理的。笔者在ModelAttributeMethodProcessor#resolveArgument()方法中查找到了相应的蛛丝马迹,列出关键的代码
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
....
Object attribute = null;
BindingResult bindingResult = null; if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
} else { // Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
} catch (BindException ex) {
.....
}
} 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)) { throw new BindException(binder.getBindingResult());
}
} // Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
.... return attribute;
}我们继续看下其下的validateIfApplicable()方法
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) { // 对参数上含有@Validated注解的进行校验器解析
Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); 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;
}
}
}上述的代码已经很简明概要了,笔者就不展开了。当然如果用户想要在出现异常的时候进行友好的返回,建议参考springboot情操陶冶-web配置(五)的异常机制文章便可迎刃而解
小结
参数的校验一般都是结合spring-context板块内的@Validated注解搭配hibernate的校验器便完成了相应的检测功能。逻辑还是很简单的,希望对大家有所帮助
作者:南柯问天 出处:http://www.cnblogs.com/question-sky/ 本文版权归本人和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
随时随地看视频