springboot情操陶冶-web配置(七)

參數校驗一般是OpenApi必作的操做,其會對不合法的輸入作統一的校驗以防止惡意的請求。本文則對參數校驗這方面做下簡單的分析html

spring.factories

讀者應該對此文件加以深入的印象,不少springboot整合第三方插件的方式均是今後配置文件去讀取的,本文關注下檢驗方面的東西。在相應的文件搜索validation關鍵字,最終定位至ValidationAutoConfiguration類,筆者這就針對此類做主要的分析java

ValidationAutoConfiguration

優先看下其頭上的註解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工廠是否已經包含了LocalValidatorFactoryBeanValidator對象,不影響大局。即便沒有配置,下述的代碼也是會註冊的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*註解方可生效。 若是你們對此有什麼補充歡迎留言,在此但願此篇對你們有所幫助

相關文章
相關標籤/搜索