參數校驗一般是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*註解方可生效。 若是你們對此有什麼補充歡迎留言,在此但願此篇對你們有所幫助