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
源碼:源碼分析
/** * 該方法定位: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被調用的位置
源碼:
@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
源碼:
/** * @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; }
總結:在分析源碼的過程當中犯了最大的錯誤就是:總想什麼都搞明白,跟蹤每一個源碼的實現,結果發現仍是沒搞懂,白白浪費了不少時間。其實在分析源碼的過程當中,不須要鑽牛角尖,把每一個都搞懂。你要搞明白你的「關注點「在哪?,不要走着走着就走偏了。不少源碼「觀其大意」就行,不必深究,否則就呵呵了。