记录Mybatis的配置之谜

栏目: 数据库 · 发布时间: 6年前

内容简介:每个现象背后都有其缘由,越离奇的bug越是由不起眼的细节引发,每个bug背后都有框架或代码运行的原理和机制所在,解决bug,不仅仅需要去网上查询,还需要对其背后的原理进行了解和总结。 同事大佬最近在学习并使用Mybatis,他使用Mybatis的MapperScannerConfigurer来进行相关配置,并希望通过yml配置来指定basePackage,mappers等属性。为此,编写了自定义的配置类为此,我们花了大量时间探查缘由,最后不得不询问了另一位大佬,才发现这个离奇问题的背后竟然有着这样的缘由。

每个现象背后都有其缘由,越离奇的bug越是由不起眼的细节引发,每个bug背后都有框架或代码运行的原理和机制所在,解决bug,不仅仅需要去网上查询,还需要对其背后的原理进行了解和总结。 同事大佬最近在学习并使用Mybatis,他使用Mybatis的MapperScannerConfigurer来进行相关配置,并希望通过yml配置来指定basePackage,mappers等属性。为此,编写了自定义的配置类 StarterAutoConfiguration 和自定义属性类 TkProperties ,并在初始化 MapperScannerConfigurer 时使用 TkProperties 中的属性。但是,事与愿违,在初始化 MapperScannerConfigurer 时, TkProperties 实例中的属性死活都是未初始化状态。

记录Mybatis的配置之谜

为此,我们花了大量时间探查缘由,最后不得不询问了另一位大佬,才发现这个离奇问题的背后竟然有着这样的缘由。 我们首先来看一下大佬关于 MapperScannerConfigurer 的自定义配置实现。他首先定义了自定义配置类 BkStarterAutoConfiguration ,使用 @EnableConfigurationProperties 注解将 TkProperties 声明为配置属性类。

@Configuration
@EnableConfigurationProperties({TkProperties.class})
@AutoConfigureBefore(MybatisAutoConfiguration.class)
public class BkStarterAutoConfiguration {
  @Bean
  @ConditionalOnMissingBean
  @Order(Ordered.HIGHEST_PRECEDENCE)
  public TkProperties tkProperties() {
    return new TkProperties();
  }
}
复制代码

下面是 TkProperties 的定义,使用 @ConfigurationProperties 注解声明了该属性配置的前缀,两个属性名称为 basePackagemappers

@Data
@ConfigurationProperties(prefix = "tk")
public class TkProperties {
  private String basePackage;
  private String mappers;
}
复制代码

MapperConfig 是声明并配置 MapperScannerConfigurer 实例的配置类,使用被 @Bean 注解修饰的 mapperScannerConfigurer 方法来初始化,其方法参数为 TkProperties

@Configuration
public class MapperConfig {
  @Bean
  public MapperScannerConfigurer mapperScannerConfigurer(TkProperties tkProperties) {
    MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
    mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
    //使用TkProperties的成员变量来配置mapperScannerConfigurer
    mapperScannerConfigurer.setBasePackage(tkProperties.getBasePackage());
    Properties properties = new Properties();
    properties.setProperty("mappers", tkProperties.getMappers());
    mapperScannerConfigurer.setProperties(properties);
    return mapperScannerConfigurer;
  }
}
复制代码

yml配置文件如下所示。

---
tk:
  basePackage: cn.remcarpediem.mybatis.dao
  mappers: cn.remcarpediem.mappers.BaseDao
复制代码

代码乍看起来一定问题都没有,但是运行时,在初始化MapperScannerConfigurer实例时,TkProperties实例的属性死活就是没有初始化成功。

记录Mybatis的配置之谜

一定有很多见多识广的读者已经知道这个现象背后的原因。“凶手”就是 MapperScannerConfigurer 实现的接口 BeanDefinitionRegistryPostProcessor 。具体原因我们还需要慢慢来解释,因为它涉及了Spring Boot的很多原理。

首先, BeanDefinitionRegistryPostProcessor 接口继承了 BeanFactoryPostProcessor 接口,大家一般都对 BeanFactoryPostProcessor 较为熟悉,它是实例工厂(BeanFactory)的后处理器(PostProcessor),与之类似的还有实例的后处理器(BeanPostProcessor)。 BeanFactoryPostProcessor 中只定义了一个方法,其将会在 ApplicationContext 内部的 BeanFactory 加载完 BeanDefinition 后,但是在Bean实例化之前进行。所以通常我们可以通过实现该接口来对实例化之前的 BeanDefinition 进行修改。比如说 PropertySourcesPlaceholderConfigurer 就实现 BeanFactoryPostProcessor 接口,用于处理实例中被 @Value 注解修饰的变量,修改其数值。

public interface BeanFactoryPostProcessor {
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
复制代码

BeanDefinitionRegistryPostProcessor 接口扩展自 BeanFactoryPostProcessor ,它是 BeanDefinitionRegistry 的后处理器,它可以在 BeanFactoryPostProcessor 检测之前注册一些特殊的 BeanDefinition ,比如说可以注册用来定义 BeanFactoryPostProcessorBeanDefintion ,比如说我们之前提到的 MapperScannerConfigurerConfigurationClassPostProcessor

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
复制代码

MapperScannerConfigurerpostProcessBeanDefinitionRegistry 主要用来 ClassPathMapperScanner 来扫描 MybatisMapperClassPathMapperScanner 继承了 ClassPathBeanDefinitionScanner ,在 doScan 方法中获取了 basePackage 指定的包路径下的所有 MapperBeanDefinition ,然后进行注册。

BeanPostProcessor 就是Bean实例的后处理器。每个Bean实例在进行初始化前会调用其 postProcessBeforeInitialization 方法和初始化之后调用其 postProcessAfterInitialization 方法。 ConfigurationPropertiesBindingPostProcessor 实现了 BeanPostProcessor 接口,用于处理被 @ConfigurationProperties 修饰的实例。

public interface BeanPostProcessor {
  Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
  Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
复制代码

我们可以总结一下 BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessorBeanPostProcessor 三个后处理器发挥作用的次序和时机。

记录Mybatis的配置之谜

由此,我们也能够理解为什么 MapperScannerConfigurer 初始化时, TkProperties 还没有初始化,那是因为 ConfigurationPropertiesBindingPostProcessor 还没有初始化,并且也没有对 TkProperties 进行处理

遇到问题和bug,不要百度一下解决方案处理就结束了,而是要深入了解一下背后的机制和原理,希望大家都能够多多探索更加深入的原理,获得更多的知识。

记录Mybatis的配置之谜

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

RESTful Web Services Cookbook

RESTful Web Services Cookbook

Subbu Allamaraju / Yahoo Press / 2010-3-11 / USD 39.99

While the REST design philosophy has captured the imagination of web and enterprise developers alike, using this approach to develop real web services is no picnic. This cookbook includes more than 10......一起来看看 《RESTful Web Services Cookbook》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具