参数校验一般是OpenApi必作的操做,其会对不合法的输入作统一的校验以防止恶意的请求。本文则对参数校验这方面做下简单的分析html
读者应该对此文件加以深入的印象,不少springboot整合第三方插件的方式均是今后配置文件去读取的,本文关注下检验方面的东西。在相应的文件搜索validation关键字,最终定位至ValidationAutoConfiguration类,笔者这就针对此类做主要的分析java
优先看下其头上的注解web
@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对象,不影响大局。即便没有配置,下述的代码也是会注册的spring
@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*注解源码springboot
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @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()*mvc
@Override public void afterPropertiesSet() { // 查找对应的类以及祖先类上是否含有@Validated注解 Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true); // 建立MethodValidationInterceptor处理类来处理具体的逻辑 this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator)); }
上述的配置代表只要某个类上使用了*@Validated注解,其相应的方法就会被校验相关的参数。笔者紧接着看下MethodValidationInterceptor#invoke()*方法app
@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*的范畴了,感兴趣的读者可自行分析~maven
即*@Validated注解做用于方法的参数上,其有关的校验则是被springmvc的参数校验器处理的。笔者在ModelAttributeMethodProcessor#resolveArgument()*方法中查找到了相应的蛛丝马迹,列出关键的代码ide
@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); // 兼容@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; } } }
上述的代码已经很简明概要了,笔者就不展开了。固然若是用户想要在出现异常的时候进行友好的返回,建议参考springboot情操陶冶-web配置(五)的异常机制文章即可迎刃而解
参数的校验通常都是结合spring-context板块内的*@Validated注解搭配hibernate的校验器便完成了相应的检测功能。 通过笔者的测试,发如今类上使用@Validated注释基本没啥用,仍是会依赖在参数上添加@Validated*注解方可生效。 若是你们对此有什么补充欢迎留言,在此但愿此篇对你们有所帮助