Springboot集成BeanValidation擴展二:加載jar中的資源文件

1、需求java

今天在搭建Springboot框架的時候,又遇到一個需求:在多模塊系統中,有些模塊想本身管理BeanValidation的資源文件(默認是啓動項目claspath下的 ValidationMessages.properties)。剛開始還天真地認爲springboot會不會幫咱們作了,結果並無,因而就是擼源碼了。web

如下是個人實現和實現原理spring

2、實現springboot

@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
/**
 * 當有異常時返回默認的驗證器
 * @return 返回的是org.springframework.validation.Validator,不是javax.validation.Validator
 * 因此返回時要適配一下
 */
@Override
public Validator getValidator() {
	//路徑匹配
	PathMatchingResourcePatternResolver resourcePatternResolver =
			new PathMatchingResourcePatternResolver(
        MyWebMvcConfigurer.class.getClassLoader());
	try {
		//匹配屬性文件,有個限制,資源文件名稱必須包含Validation
		Resource[] resources = 
            resourcePatternResolver.getResources("classpath*:*Validation*.properties");

		List<String> files = Arrays.stream(resources)
				.filter(resource -> StringUtils.isNotBlank(resource.getFilename()))
				.map(resource -> {
					String fileName = resource.getFilename();
					return fileName.substring(0, fileName.indexOf("."));
				}).collect(Collectors.toList());

		javax.validation.Validator validator = Validation.byDefaultProvider()
				.configure()
            	 //這裏能夠加載多個文件
				.messageInterpolator(new ResourceBundleMessageInterpolator(
                    new AggregateResourceBundleLocator(files)))
				.buildValidatorFactory()
				.getValidator();
        //適配
		return new SpringValidatorAdapter(validator);
	} catch (IOException e) {
       	//發生異常,返回null,springboot框架會採用默認的validator
		return null;
	}
}

3、實現原理mvc

源碼分析app

一、定位Bean在什麼地方驗證的框架

DispatcherServlet驗證Bean的主要源碼路徑ide

  • RequestResponseBodyMethodProcessor#resolveArgument
    • AbstractMessageConverterMethodArgumentResolver#validateIfApplicable
      • DataBinder#validate(核心)

源碼:源碼分析

/**
 * 該方法定位:org.springframework.web.servlet.mvc.method.annotation.
 			RequestResponseBodyMethodProcessor#resolveArgument
 			
 * Throws MethodArgumentNotValidException if validation fails.
 * @throws HttpMessageNotReadableException if {@link RequestBody#required()}
 * is {@code true} and there is no body content or if there is no suitable
 * converter to read the content with.
 */
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) 
    throws Exception {

	parameter = parameter.nestedIfOptional();
	Object arg = readWithMessageConverters(webRequest, parameter, 
                                           parameter.getNestedGenericParameterType());
	String name = Conventions.getVariableNameForParameter(parameter);

	if (binderFactory != null) {
		WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
		if (arg != null) {
            //*******校驗方法參數是否符合要求*******
            //調用鏈:也就是驗證器被調用的地方
			validateIfApplicable(binder, parameter);
			if (binder.getBindingResult().hasErrors() 
                && isBindExceptionRequired(binder, parameter)) {
				throw 
                    new MethodArgumentNotValidException(parameter, 
                                                        binder.getBindingResult());
			}
		}
		if (mavContainer != null) {
			mavContainer.addAttribute(
                BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
		}
	}

	return adaptArgumentIfNecessary(arg, parameter);
}

/**
 * 該方法定位:org.springframework.web.servlet.mvc.method.annotation.
    AbstractMessageConverterMethodArgumentResolver#validateIfApplicable
 *  
 * Validate the binding target if applicable.
 * <p>The default implementation checks for {@code @javax.validation.Valid},
 * Spring's {@link org.springframework.validation.annotation.Validated},
 * and custom annotations whose name starts with "Valid".
 * @param binder the DataBinder to be used
 * @param parameter the method parameter descriptor
 * @since 4.1.5
 * @see #isBindExceptionRequired
 */
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
	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;
		}
	}
}


/**
 * 該方法定位:org.springframework.validation.DataBinder#validate(java.lang.Object...)
 *
 * Invoke the specified Validators, if any, with the given validation hints.
 * <p>Note: Validation hints may get ignored by the actual target Validator.
 * @param validationHints one or more hint objects to be passed to a {@link SmartValidator}
 * @see #setValidator(Validator)
 * @see SmartValidator#validate(Object, Errors, Object...)
 * 核心方法
 */
public void validate(Object... validationHints) {
	for (Validator validator : getValidators()) {
		if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
			((SmartValidator) validator).validate(getTarget(), 
                                                  getBindingResult(), validationHints);
		}
		else if (validator != null) {
			validator.validate(getTarget(), getBindingResult());
		}
	}
}
/**
 * 獲取驗證器(關鍵就在:this.validators怎麼初始化的?)
 */
public List<Validator> getValidators() {
	return Collections.unmodifiableList(this.validators);
}

發現:在DataBinder#validate中有驗證Bean的核心代碼validator.validate(...)ui

分析到這裏關鍵就是validator在哪賦值的?

二、validators賦值

  • DataBinder屬性validators賦值 private final List<Validator> validators = new ArrayList<>();

    //斷點跟蹤發現:
    public void setValidator(@Nullable Validator validator) {
        assertValidators(validator);
        this.validators.clear();
        if (validator != null) {
            this.validators.add(validator);
        }
    
    }
    • DataBinder#setValidator被調用的位置

      • org.springframework.web.bind.support.ConfigurableWebBindingInitializer#initBinder

      源碼:

      @Override
      public void initBinder(WebDataBinder binder) {
      	binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
      	if (this.directFieldAccess) {
      		binder.initDirectFieldAccess();
      	}
      	if (this.messageCodesResolver != null) {
      		binder.setMessageCodesResolver(this.messageCodesResolver);
      	}
      	if (this.bindingErrorProcessor != null) {
      		binder.setBindingErrorProcessor(this.bindingErrorProcessor);
      	}
      	if (this.validator != null && binder.getTarget() != null &&
      			this.validator.supports(binder.getTarget().getClass())) {
      		//發現是在這裏調用的,下面的問題就是ConfigurableWebBindingInitializer
      		//中的validator屬性在哪初始化的?
      		//在對應的setValidator方法打斷點
      		binder.setValidator(this.validator);
      	}
      	if (this.conversionService != null) {
      		binder.setConversionService(this.conversionService);
      	}
      	if (this.propertyEditorRegistrars != null) {
      		for (PropertyEditorRegistrar propertyEditorRegistrar : 		   
                   this.propertyEditorRegistrars) {
      			propertyEditorRegistrar.registerCustomEditors(binder);
      		}
      	}
      }
    • ConfigurableWebBindingInitializer#initBinder被調用的位置 研究發現:ConfigurableWebBindingInitializer#initBinder是在springboot初始化時被調用的 調用鏈以下: 調用鏈1:初始化springmvc的requestMappingHandlerAdapter EnableWebMvcConfiguration#requestMappingHandlerAdapter

      • super.requestMappingHandlerAdapter();—>WebMvcConfigurationSupport 調用鏈2: WebMvcConfigurationSupport#requestMappingHandlerAdapter
      • adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer()); 調用鏈3: EnableWebMvcConfiguration#ConfigurableWebBindingInitializer
      • super.getConfigurableWebBindingInitializer(); 調用鏈4: WebMvcConfigurationSupport#ConfigurableWebBindingInitializer
      • mvcValidator(),這個是核心

      源碼:

      /**
       * @see org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.
       * EnableWebMvcConfiguration#requestMappingHandlerAdapter
       */
      @Bean
      @Override
      public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
          //調用鏈1:調用父類WebMvcConfigurationSupport#requestMappingHandlerAdapter
      	RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
      	adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null
      			|| this.mvcProperties.isIgnoreDefaultModelOnRedirect());
      	return adapter;
      }
      
      /**
       * @seeorg.springframework.web.servlet.config.annotation.
       * WebMvcConfigurationSupport#requestMappingHandlerAdapter
       *
       */
      public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
      	RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
      	adapter.setContentNegotiationManager(mvcContentNegotiationManager());
      	adapter.setMessageConverters(getMessageConverters());
          //調用鏈2:EnableWebMvcConfiguration#getConfigurableWebBindingInitializer
      	adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
      	adapter.setCustomArgumentResolvers(getArgumentResolvers());
      	adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
      	...
      }
      
      /**
       * @see springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.
       * EnableWebMvcConfiguration#getConfigurableWebBindingInitializer
       *
       */
      @Override
      protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
      	try {
              //這裏是不存在實例,報異常
      		return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
      	}
      	catch (NoSuchBeanDefinitionException ex) {
              //調用鏈3:WebMvcConfigurationSupport#getConfigurableWebBindingInitializer
      		return super.getConfigurableWebBindingInitializer();
      	}
      }
      
      /**
       * @seespringframework.web.servlet.config.annotation.WebMvcConfigurationSupport
       * #getConfigurableWebBindingInitializer
       *
       */
      protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
      	ConfigurableWebBindingInitializer initializer = 
      	new ConfigurableWebBindingInitializer();
      	initializer.setConversionService(mvcConversionService());
          //調用鏈4:核心方法mvcValidator()
      	initializer.setValidator(mvcValidator());
      	MessageCodesResolver messageCodesResolver = getMessageCodesResolver();
      	if (messageCodesResolver != null) {
      		initializer.setMessageCodesResolver(messageCodesResolver);
      	}
      	return initializer;
      }

三、validator是什麼

經過源碼分析,找到了關鍵點就是mvcValidator(),如今對其分析,找出其返回的validator究竟是什麼?

斷點調試時發現mvcValidator()進入了

org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#intercept

  • org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#resolveBeanReference resolveBeanReference方法裏有個關鍵的代碼

    //關鍵在於 beanFactory.getBean(beanName),name = "mvcValidator",建立該實例
    //從而會找到EnableWebMvcConfiguration的mvcValidator方法
    //(由於mvcValidator方法上有@Bean,方法名稱又與beanName相同,故調用)
    Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
                           beanFactory.getBean(beanName));
  • org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration#mvcValidator 終於找到建立validator對象的點了,如下就是如何本身擴展? 繼續研究建立validator的源碼,尋找關鍵點

    @Bean
    @Override
    public Validator mvcValidator() {
    	if (!ClassUtils.isPresent("javax.validation.Validator",
                                  getClass().getClassLoader())) {
    		return super.mvcValidator();
    	}
        //關鍵在於getValidator()方法
        //真正調用的是父類DelegatingWebMvcConfiguration#getValidator
    	return ValidatorAdapter.get(getApplicationContext(), getValidator());
    }

四、關鍵點:分析getValidator()方法

注意:這裏就是咱們能夠擴展的地方

/**
 * springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration#getValidator
 */
@Override
@Nullable
protected Validator getValidator() {
	//configurers屬性是WebMvcConfigurerComposite的對象
	return this.configurers.getValidator();
}

/**
 *@see springframework.web.servlet.config.annotation.WebMvcConfigurerComposite#getValidator
 */
@Override
public Validator getValidator() {
	Validator selected = null;
    //看到WebMvcConfigurer這個東西,我是很激動呀!終於看到曙光了,激動半天
    //因而我就自定義了MyWebMvcConfigurer實現WebMvcConfigurer,並重寫
    //其中的getValidator方法,哈哈,終於找到擴展點了
	for (WebMvcConfigurer configurer : this.delegates) {
		Validator validator = configurer.getValidator();
		if (validator != null) {
			if (selected != null) {
				throw new IllegalStateException("No unique Validator found: {" +
						selected + ", " + validator + "}");
			}
			selected = validator;
		}
	}
	return selected;
}

經過getValidator()獲取自定義的validator後

ValidatorAdapter.get(getApplicationContext(), getValidator());對其包裝以下:

/**
 * @see springframework.boot.autoconfigure.validation.ValidatorAdapter#get
 */
public static Validator get(ApplicationContext applicationContext,
		Validator validator) {
    //若是爲null(自定義的validator發生異常),返回默認的
	if (validator != null) {
        //由於非空,會執行該行代碼
		return wrap(validator, false);
	}
	return getExistingOrCreate(applicationContext);
}

private static Validator wrap(Validator validator, boolean existingBean) {
	if (validator instanceof javax.validation.Validator) {
        //執行該代碼
		if (validator instanceof SpringValidatorAdapter) {
			return new ValidatorAdapter((SpringValidatorAdapter) validator,
					existingBean);
		}
		return new ValidatorAdapter(
				new SpringValidatorAdapter((javax.validation.Validator) validator),
				existingBean);
	}
	return validator;
}

總結:在分析源碼的過程當中犯了最大的錯誤就是:總想什麼都搞明白,跟蹤每一個源碼的實現,結果發現仍是沒搞懂,白白浪費了不少時間。其實在分析源碼的過程當中,不須要鑽牛角尖,把每一個都搞懂。你要搞明白你的「關注點「在哪?,不要走着走着就走偏了。不少源碼「觀其大意」就行,不必深究,否則就呵呵了。

相關文章
相關標籤/搜索