一、需求

今天在搭建Springboot框架的时候,又遇到一个需求:在多模块系统中,有些模块想自己管理BeanValidation的资源文件(默认是启动项目claspath下的 ValidationMessages.properties)。刚开始还天真地认为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 {//匹配属性文件,有个限制,资源文件名称必须包含ValidationResource[] 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框架会采用默认的validatorreturn null;}
}

三、实现原理

源码分析

1、定位Bean在什么地方验证的

DispatcherServlet验证Bean的主要源码路径

  • 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(...)

分析到这里关键就是validator在哪赋值的?

2、validators赋值

  • DataBinder属性validators赋值
    private final List 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#requestMappingHandlerAdapterRequestMappingHandlerAdapter 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#getConfigurableWebBindingInitializeradapter.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#getConfigurableWebBindingInitializerreturn 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;
      }

3、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#getValidatorreturn ValidatorAdapter.get(getApplicationContext(), getValidator());
    }

4、关键点:分析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;
}

总结:在分析源码的过程中犯了最大的错误就是:总想什么都搞明白,跟踪每个源码的实现,结果发现还是没搞懂,白白浪费了很多时间。其实在分析源码的过程中,不需要钻牛角尖,把每个都搞懂。你要搞明白你的“关注点“在哪?,不要走着走着就走偏了。很多源码“观其大意”就行,没必要深究,不然就呵呵了。

转载于:https://www.cnblogs.com/liruiloveparents/p/9400426.html

Springboot集成BeanValidation扩展二:加载jar中的资源文件相关推荐

  1. python3读取网页_python3+selenium获取页面加载的所有静态资源文件链接操作

    软件版本: python 3.7.2 selenium 3.141.0 pycharm 2018.3.5 具体实现流程如下,废话不多说,直接上代码: from selenium import webd ...

  2. Android 插件化之—— 加载插件中的资源

    Android 资源分类: res目录下的资源 res目录下的资源可以通过Resource对象进行访问,通过分析Resource源码可知,Resource访问res目录下的资源其实还是调用的Asset ...

  3. 安卓加载asset中的json文件_Joomla 4中的Web资源介绍

    Joomla 4中我最喜欢的改进之一是"Web资源"特性.它允许你通过一次调用按特定顺序加载一组JavaScript和CSS文件. 比方说,你希望加载依赖于其他文件的CSS或Jav ...

  4. ios 加载本地html及资源文件

    把本地html目录添加到xcode html资源显示蓝色文件夹 WKWebView加载本地html资源 NSString *filePath = [[NSBundle mainBundle] path ...

  5. Springboot集成BeanValidation扩展一:错误提示信息加公共模板

    Bean Validator扩展 1.需求 ​ 在使用validator时,有个需求就是公用错误提示信息,什么意思? 举个例子: ​ @NotEmpty非空判断,在资源文件中我不想每个非空判断都写&q ...

  6. Maven——spring mvc加载js css静态资源文件

    之前试过<mvc:resources>和<mvc:default-servlet-handler>,都不管用.经过尝试采用了下面的方法,加载成功. 首先是目录结构: commo ...

  7. 获取jar中的资源文件途径

    2019独角兽企业重金招聘Python工程师标准>>> 如果资源文件处于jar中而非当前项目中,可以通过如下方式加载 String path = Thread.currentThre ...

  8. 安卓加载asset中的json文件_Android中读取asset路径下本地json文件

    最近自己写个小demo,用到了assets文件里面的内容.猛的没想起来怎么使用.就记录下来 移动端开发中,和后台的交互方式不外乎json和xml,由于json的轻量级以及易用性,所以现在的交互协议基本 ...

  9. 安卓加载asset中的json文件_Android解析Asset目录下的json文件

    在app module中的src/main/assets目录下我们准备了两个json文件: destination.json如下: { "main/tabs/sofa": { &q ...

最新文章

  1. 浅谈WebService的调用转
  2. PowerDesigner中NAME和COMMENT的互相转换,需要执行语句
  3. currentThread()方法的作用
  4. pytorch torch.from_numpy()(从numpy数组创建一个张量,数组和张量共享相同内存)
  5. AtCoder AGC030B Tree Burning
  6. [导入]C#实现Des加密和解密
  7. 解决scrollViewDidScroll do not work的方法
  8. 实习日志(1)2011-12-30
  9. android room数据库embed,Android room数据库基操
  10. Linux安装webmin
  11. Mysql数据库内连接INNER JOIN的使用
  12. laravel安装指定版本
  13. 音视频中的帧I 帧,B帧,P帧,IDR帧理解
  14. php分页类 seo,laravel 分页seo浅谈
  15. 计算两个数据的百分比
  16. BP算法推导(python实现)
  17. 可积 连续 可微 可导关系
  18. 如何使用ReadProcessMemory读取多重指针
  19. 五子棋(C++面向对象实现)
  20. 阿噗啊噗服务器维护,这些App我能笑一年!阿噗整理的20个奇葩App,没玩过你就OUT了!...

热门文章

  1. 机器数.原码 反码 补码比较理解
  2. java第七章第九题_Java2程序设计基础第七章课后习题
  3. [2016百度之星 - 初赛(Astar Round2A)]Snacks
  4. LSH︱python实现MinHash-LSH及MinHash LSH Forest——datasketch(四)
  5. [机器学习]关联挖掘介绍
  6. 导入导出mysql数据库命令
  7. 学习产品型是否要满足人们的“懒”需求
  8. java ArrayList倒序
  9. Spring MVC PathVariable
  10. Java序列化中的SerialVersionUid