聊一聊MyBatis的核心概念、Spring相关的核心内容,主要结合源码理解Spring是如何整合MyBatis的。(结合右侧目录了解吧)

创建SqlSession的工厂

sql请求的会话,通过SqlSessionFactory获取。

  1. String resource = "mybatis-config.xml";
  2. InputStream resourceAsStream = Resources.getResourceAsStream(resource);
  3. SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
  4. SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream); // 通过Builder获取构建SqlSessionFactory(读取mybatis-config.xml文件配置)
  5. SqlSession sqlSession = sqlSessionFactory.openSession(); // 开启Session
  6. UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  7. User user = userMapper.findByUserId(1);

上述代码就是单独使用MyBatis的时候的API例子。读取mybatis-config.xml构建出SqlSessionFactory,通过Factory开启SqlSession,使用SqlSession获取Mapper的代理实例。

  1. public interface UserMapper {
  2. User findByUserId(Integer userId);
  3. }
  1. <mapper namepsace = "com.deepz.mybatis.user.UserMapper">
  2. <select id = "findByUserId" resultType="User">
  3. ...
  4. </select>
  5. </mapper>

以上就是咱们熟悉的MyBatis使用代码了,一个Mapper接口对应的就有一个XML文件。

是一种特殊的SpringBean,对应的真实实例是FactoryBean接口中getObject()方法的返回值,用于自定义复杂的Bean生成。

AbstractBeanFactory#doGetBean

  1. Object sharedInstance = getSingleton(beanName); // 从三级缓存中根据beanName获取SpringBean
  2. if (sharedInstance != null && args == null) { // 如果SpringBean不为空则说明命中缓存,直接获取SpringBean实例即可
  3. if (logger.isDebugEnabled()) {
  4. if (isSingletonCurrentlyInCreation(beanName)) {
  5. logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
  6. "' that is not fully initialized yet - a consequence of a circular reference");
  7. }
  8. else {
  9. logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
  10. }
  11. }
  12. bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); // 获取SpringBean真实实例(针对FactoryBean)
  13. }

AbstractBeanFactory#getObjectForBeanInstance

  1. // Now we have the bean instance, which may be a normal bean or a FactoryBean.
  2. // If it's a FactoryBean, we use it to create a bean instance, unless the
  3. // caller actually wants a reference to the factory.
  4. if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) { // 如果不是FactoryBean类型则直接返回
  5. return beanInstance;
  6. }
  7. // 后续代码针对FactoryBean类型的SpringBean处理
  8. Object object = null;
  9. if (mbd == null) {
  10. object = getCachedObjectForFactoryBean(beanName); // 从缓存中获取
  11. }
  12. if (object == null) {
  13. // Return bean instance from factory.
  14. FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
  15. // Caches object obtained from FactoryBean if it is a singleton.
  16. if (mbd == null && containsBeanDefinition(beanName)) {
  17. mbd = getMergedLocalBeanDefinition(beanName);
  18. }
  19. boolean synthetic = (mbd != null && mbd.isSynthetic());
  20. object = getObjectFromFactoryBean(factory, beanName, !synthetic); // 获取FactoryBean真实实例
  21. }
  22. return object;

FactoryBeanRegistrySupport#doGetObjectFromFactoryBean
FactoryBeanRegistrySupport#getObjectFromFactoryBean最终会调到如下方法,通过FactoryBean的getObject()获取FactoryBean的真实实例

  1. Object object;
  2. try {
  3. if (System.getSecurityManager() != null) {
  4. AccessControlContext acc = getAccessControlContext();
  5. try {
  6. object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
  7. @Override
  8. public Object run() throws Exception {
  9. return factory.getObject();
  10. }
  11. }, acc);
  12. }
  13. catch (PrivilegedActionException pae) {
  14. throw pae.getException();
  15. }
  16. }
  17. else {
  18. object = factory.getObject(); // 显示调用FactoryBean#getObject
  19. }
  20. }

在SpringBean的生命周期中,Bean的初始化环节Spring会调用AbstractAutowireCapableBeanFactory#invokeInitMethods() 回调实现了InitializingBean接口的Bean的InitializingBean#afterPropertiesSet()

关于SpringBean生命周期欢迎移步笔者的相关总结随笔《深入源码理解SpringBean生命周期

  1. protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
  2. throws Throwable {
  3. boolean isInitializingBean = (bean instanceof InitializingBean); // Bean实现了InitializingBean接口
  4. if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
  5. if (logger.isDebugEnabled()) {
  6. logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
  7. }
  8. if (System.getSecurityManager() != null) {
  9. try {
  10. AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
  11. @Override
  12. public Object run() throws Exception {
  13. ((InitializingBean) bean).afterPropertiesSet();
  14. return null;
  15. }
  16. }, getAccessControlContext());
  17. }
  18. catch (PrivilegedActionException pae) {
  19. throw pae.getException();
  20. }
  21. }
  22. else {
  23. ((InitializingBean) bean).afterPropertiesSet(); // 显式回调InitializingBean的afterPropertiesSet()方法
  24. }
  25. }
  26. if (mbd != null) {
  27. String initMethodName = mbd.getInitMethodName();
  28. if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
  29. !mbd.isExternallyManagedInitMethod(initMethodName)) {
  30. invokeCustomInitMethod(beanName, bean, mbd);
  31. }
  32. }
  33. }

可以看到BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor,那么该接口的实现类一定会在Spring应用上下文生命周期中回调相关接口方法。

  1. public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
  2. /**
  3. * Modify the application context's internal bean definition registry after its
  4. * standard initialization. All regular bean definitions will have been loaded,
  5. * but no beans will have been instantiated yet. This allows for adding further
  6. * bean definitions before the next post-processing phase kicks in.
  7. * @param registry the bean definition registry used by the application context
  8. * @throws org.springframework.beans.BeansException in case of errors
  9. */
  10. void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
  11. }

这个接口是干什么的?它有自己的抽象方法,在Spring应用上下文生命周期的”invokeBeanFactoryPostProcessors”环节会回调相关方法。(这里不是重点不过多聊)

我们这次关注的重点是它的子接口-BeanDefinitionRegistryPostProcessor,Spring在上述环节中对该接口做了特殊处理,回调了它的独有方法。(如上面的代码段所示)

AbstractApplicationContext#refresh()方法即为Spring应用上下文的生命周期的刷新入口,可以看到在比较前置的环节就会先处理BeanFactoryProcessor类型的Bean。

追进源码后发现最后会在PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()方法中循环处理所有BeanFactoryProcessor,其中如果是BeanDefinitionRegistryPostProcessor则会回调BeanDefinitionRegistryPostProcessor的独有方法,值得注意的是该方法的入参是一个BeanDefinitionRegistry

(关于BeanDefinitionRegistry有必要在单独段落做介绍,所以请先移步下一段落吧)

在了解了这几个接口后,我们汇总一下就明白了BeanDefinitionRegistryPostProcessor的能力了。它可以在Spring应用上下文前期先被实例化且回调相关接口方法,向Spring容器注册或移除BeanDefinition甚至可以在get出一个BeanDefinition后直接修改内部属性,让Bean变成你想要的模样。

BeanDefinition是Spring实例化一个Bean的依据,它的内部维护了一个Bean的各种属性,如BeanClass、BeanName、lazyInit(是否懒加载)、primary、scope等等。

而Spring在实例化一个Bean的时候需要先从一个Map中根据beanName获取到对应的BeanDefinition才能去按需实例化SpringBean,如下。

DefaultListableBeanFactory#getBeanDefinition()

看看这个Map的定义
BeanDefinition

相信大家也猜到了,上面提到的就是维护这个Map的。
BeanDefinitionRegistry接口的方法如下:

可以看到该接口的能力就是维护Spring容器中的BeanDefinition。

有了上面的回顾后,关于Spring整合MyBatis的秘密就很容易揭晓了。

在跟进源码前,我们先思考下如果是你,你会怎么将MyBatis整合进来?达到效果:通过@Autowired将Mapper注入进来便可以直接使用。

首先,我们有了Spring,第一个要干掉的就是SqlSessionFactory的维护了,我们要想办法读取”mybatis-config.xml“配置后,将SqlSessionFactory作为一个SpringBean交给SpringIOC管理。

其次,同样的,我们不可能每次都通过sqlSession.getMapper()来获取我们需要的Mapper代理实例,所以第二个要干掉的就是Mapper的维护,我们同样要想办法将所有的Mapper处理成SpringBean交给SpringIOC,这样我们就能够将SpringBean依赖注入到任何地方了。

思考过后,我们来看看Spring是怎么做的吧。当我们需要结合Spring使用MyBatis的时候,第一步便是添加一个mybatis-spring的Jar到项目里来,那么秘密都在这里了。

如下便是MyBatis-Spring这个Jar的项目结构了,应该能看到大家使用的时候熟悉的组件吧。如MapperScan注解。眼尖的伙伴应该能看到几个类:SqlSessionFactoryBean、MapperFactoryBean、MapperScannerConfigurer、SpringManagedTransaction,这几个类将会是接下来探讨的重点。
MyBatis-Spring

刚刚我们聊到了,首先需要干掉的就是SqlSessionFactory的低端维护方式。我们先看看SqlSessionFactory这个类的继承树。

SqlSessionFactoryBean继承树
可以看到它实现了FactoryBean,这就意味着它一定有一个getObject()方法,用于返回交给Spring管理的实例;
它还实现了InitializingBean,这就意味着在这个Bean的初始化时,Spring会回调它的afterPropertiesSet()方法。(Spring事件本次不讨论)

我们先看看,这个SqlSessionFactoryBean交给Spring管理的对象是怎样构建的。

  1. /**
  2. * {@inheritDoc}
  3. */
  4. @Override
  5. public SqlSessionFactory getObject() throws Exception {
  6. if (this.sqlSessionFactory == null) { // 如果sqlSessionFactory为空,则显式调用afterPropertiesSet()方法
  7. afterPropertiesSet();
  8. }
  9. return this.sqlSessionFactory; // 返回sqlSessionFactory
  10. }
  11. @Override
  12. public Class<? extends SqlSessionFactory> getObjectType() {
  13. return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
  14. }

可以看到源码中首先是判空,如果为空则显式调用afterPropertiesSet()方法,最后将sqlSessionFactory返回。那么可以猜出,afterPropertiesSet()方法大概率是构造sqlSessionFactory的了。

首先看看afterPropertiesSet()方法

  1. /**
  2. * {@inheritDoc}
  3. */
  4. @Override
  5. public void afterPropertiesSet() throws Exception {
  6. notNull(dataSource, "Property 'dataSource' is required");
  7. notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
  8. state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
  9. "Property 'configuration' and 'configLocation' can not specified with together");
  10. this.sqlSessionFactory = buildSqlSessionFactory(); // 核心逻辑在buildSqlSessionFactory()中。
  11. }

跟进看看buildSqlSessionFactory()方法,请重点看标了注释的,其他的可以不用太关注细节,主要是一些配置的初始化工作。

  1. protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
  2. Configuration configuration;
  3. XMLConfigBuilder xmlConfigBuilder = null;
  4. // 初始化Configuration全局配置对象。
  5. if (this.configuration != null) {
  6. configuration = this.configuration;
  7. if (configuration.getVariables() == null) {
  8. configuration.setVariables(this.configurationProperties);
  9. } else if (this.configurationProperties != null) {
  10. configuration.getVariables().putAll(this.configurationProperties);
  11. }
  12. } else if (this.configLocation != null) {
  13. // 读取指定的配置文件
  14. xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
  15. configuration = xmlConfigBuilder.getConfiguration();
  16. } else {
  17. if (LOGGER.isDebugEnabled()) {
  18. LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
  19. }
  20. configuration = new Configuration();
  21. if (this.configurationProperties != null) {
  22. configuration.setVariables(this.configurationProperties);
  23. }
  24. }
  25. if (this.objectFactory != null) {
  26. configuration.setObjectFactory(this.objectFactory);
  27. }
  28. if (this.objectWrapperFactory != null) {
  29. configuration.setObjectWrapperFactory(this.objectWrapperFactory);
  30. }
  31. if (this.vfs != null) {
  32. configuration.setVfsImpl(this.vfs);
  33. }
  34. // 如下将是对MyBatis的基础配置做初始化,如扫描注册别名、注册Plugins、注册TypeHandler、配置缓存、配置数据源等等。
  35. if (hasLength(this.typeAliasesPackage)) {
  36. String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
  37. ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
  38. for (String packageToScan : typeAliasPackageArray) {
  39. configuration.getTypeAliasRegistry().registerAliases(packageToScan,
  40. typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
  41. if (LOGGER.isDebugEnabled()) {
  42. LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
  43. }
  44. }
  45. }
  46. if (!isEmpty(this.typeAliases)) {
  47. for (Class<?> typeAlias : this.typeAliases) {
  48. configuration.getTypeAliasRegistry().registerAlias(typeAlias);
  49. if (LOGGER.isDebugEnabled()) {
  50. LOGGER.debug("Registered type alias: '" + typeAlias + "'");
  51. }
  52. }
  53. }
  54. if (!isEmpty(this.plugins)) {
  55. for (Interceptor plugin : this.plugins) {
  56. configuration.addInterceptor(plugin);
  57. if (LOGGER.isDebugEnabled()) {
  58. LOGGER.debug("Registered plugin: '" + plugin + "'");
  59. }
  60. }
  61. }
  62. if (hasLength(this.typeHandlersPackage)) {
  63. String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
  64. ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
  65. for (String packageToScan : typeHandlersPackageArray) {
  66. configuration.getTypeHandlerRegistry().register(packageToScan);
  67. if (LOGGER.isDebugEnabled()) {
  68. LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
  69. }
  70. }
  71. }
  72. if (!isEmpty(this.typeHandlers)) {
  73. for (TypeHandler<?> typeHandler : this.typeHandlers) {
  74. configuration.getTypeHandlerRegistry().register(typeHandler);
  75. if (LOGGER.isDebugEnabled()) {
  76. LOGGER.debug("Registered type handler: '" + typeHandler + "'");
  77. }
  78. }
  79. }
  80. if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
  81. try {
  82. configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
  83. } catch (SQLException e) {
  84. throw new NestedIOException("Failed getting a databaseId", e);
  85. }
  86. }
  87. if (this.cache != null) {
  88. configuration.addCache(this.cache);
  89. }
  90. if (xmlConfigBuilder != null) {
  91. try {
  92. xmlConfigBuilder.parse();
  93. if (LOGGER.isDebugEnabled()) {
  94. LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
  95. }
  96. } catch (Exception ex) {
  97. throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
  98. } finally {
  99. ErrorContext.instance().reset();
  100. }
  101. }
  102. // 配置事务管理类,将不再由MyBatis管理(之前配置为“JDBC”对应的MyBatis的JdbcTransactionFactory)。
  103. if (this.transactionFactory == null) {
  104. this.transactionFactory = new SpringManagedTransactionFactory();
  105. }
  106. configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
  107. // 扫描Mapper.xml以及对应的Mapper接口
  108. if (!isEmpty(this.mapperLocations)) {
  109. for (Resource mapperLocation : this.mapperLocations) {
  110. if (mapperLocation == null) {
  111. continue;
  112. }
  113. try {
  114. XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
  115. configuration, mapperLocation.toString(), configuration.getSqlFragments());
  116. xmlMapperBuilder.parse();
  117. } catch (Exception e) {
  118. throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
  119. } finally {
  120. ErrorContext.instance().reset();
  121. }
  122. if (LOGGER.isDebugEnabled()) {
  123. LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
  124. }
  125. }
  126. } else {
  127. if (LOGGER.isDebugEnabled()) {
  128. LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
  129. }
  130. }
  131. //前置条件准备好后,创建SqlSessionFactory对象
  132. return this.sqlSessionFactoryBuilder.build(configuration);
  133. }

总体就是读取MyBatis的配置,初始化Configuration全局配置对象,根据整合的配置创建出SqlSessionFactory。

经过了上述步骤,SqlSessionFactory就可以被交给Spring管理了,解决了第一个问题,可以从Spring上下文中获取到SqlSessionFactory这个Bean了。

  1. ClassPathXmlApplication applicationContext = new ClassPathXmlApplicationContext("spring.xml");
  2. SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
  3. SqlSession sqlSession = sqlSessionFactory.openSession();
  4. // 省略后续MyBatis代码 -.-
  5. ......

接下来讨论Spring如何实现通过@Autowired将Mapper注入进来后就能直接使用的问题。

首先思考一下,如何将一个类交给Spring管理?@Component系列注解?而MyBatis是一个个Interface而不是Class,在上面加注解是没用的,我们需要的是将MyBatis对Mapper生成的代理对象交给Spring管理。那该怎么做呢?Spring的做法是将Mapper一对一地包装成了MapperFactoryBean,而MapperFactoryBean维护了Mapper的类型,通过该类型获取Mapper代理实例。

MapperFactoryBean的继承树
可以看到这个MapperFactoryBean同样实现了FactoryBean接口,那么按照惯例我们看看它的getObject()做了什么。

  1. // Mapper接口类型
  2. private Class<T> mapperInterface;
  3. /**
  4. * {@inheritDoc}
  5. */
  6. @Override
  7. public T getObject() throws Exception {
  8. // 与MyBatis单独使用类似,都是通过sqlSession调用getMapper()方法获取对应的Mapper。
  9. // 需要注意的是入参是一个接口类型,而出参是MyBatis生成的代理对象
  10. return getSqlSession().getMapper(this.mapperInterface);
  11. }
  12. /**
  13. * {@inheritDoc}
  14. */
  15. @Override
  16. public Class<T> getObjectType() {
  17. return this.mapperInterface; // Object的类型
  18. }

可以看出Spring将Mapper包装成了MapperFactoryBean,其中的mapperInterface字段就是Mapper的类型,在交给Spring管理的时候依旧是通过sqlSession.getMapper(Class type)返回Mapper的代理对象的。

那么Mapper对应的模型有了,是不是还缺点什么?是的,我们需要扫描所有的Mapper,将他们包装成MapperFactoryBean(如UserMapper,就需要有一个MapperFactoryBean,其中mapperInterface字段是UserMapper.class)。这个重要的任务,Spring交给了我们接下来要聊的MapperScannerConfigurer了,通过类名就能感知到关键字:[Mapper、扫描、配置]

MapperScannerConfigurer

老规矩,看看几个核心接口的方法都做了什么。

  1. /**
  2. * {@inheritDoc}
  3. */
  4. @Override
  5. public void afterPropertiesSet() throws Exception {
  6. // 几乎啥也没干,就断言了个扫描包路径不为空。 下一个!
  7. notNull(this.basePackage, "Property 'basePackage' is required");
  8. }

可以看到MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,文章开头的Spring技术回顾聊到该接口的postProcessBeanDefinitionRegistry()方法会在Spring容器启动的时候在较早的时机被回调。

  1. private String basePackage;
  2. private boolean addToConfig = true;
  3. private SqlSessionFactory sqlSessionFactory;
  4. private SqlSessionTemplate sqlSessionTemplate;
  5. private String sqlSessionFactoryBeanName;
  6. private String sqlSessionTemplateBeanName;
  7. private Class<? extends Annotation> annotationClass;
  8. private Class<?> markerInterface;
  9. private ApplicationContext applicationContext;
  10. private String beanName;
  11. private boolean processPropertyPlaceHolders;
  12. private BeanNameGenerator nameGenerator;
  13. /**
  14. * {@inheritDoc}
  15. *
  16. * @since 1.0.2
  17. */
  18. @Override
  19. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  20. if (this.processPropertyPlaceHolders) {
  21. // 解析并更新Spring配置文件中MapperScannerConfigurer相关的配置
  22. processPropertyPlaceHolders();
  23. }
  24. //创建类路径Mapper扫描器,并配置基本信息如扫描的注解(过滤条件)等。
  25. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  26. scanner.setAddToConfig(this.addToConfig);
  27. scanner.setAnnotationClass(this.annotationClass);
  28. scanner.setMarkerInterface(this.markerInterface);
  29. scanner.setSqlSessionFactory(this.sqlSessionFactory);
  30. scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  31. scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  32. scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  33. scanner.setResourceLoader(this.applicationContext);
  34. scanner.setBeanNameGenerator(this.nameGenerator);
  35. scanner.registerFilters();
  36. //根据配置好的信息去扫描basePackage字段中指定的包及其子包
  37. scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  38. }

我们来看看processPropertyPlaceHolders()做了什么。[可以跳过,不重要]

  1. /*
  2. * BeanDefinitionRegistries are called early in application startup, before
  3. * BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been
  4. * loaded and any property substitution of this class' properties will fail. To avoid this, find
  5. * any PropertyResourceConfigurers defined in the context and run them on this class' bean
  6. * definition. Then update the values.
  7. */
  8. // 上面Spring官方的注释的意思如下:BeanDefinitionRegistriy在Spring启动的时候回调地太早了,在BeanFactoryPostProcessors之后(PropertyResourceConfigurer实现了BeanFactoryProcessor)
  9. // 方法调用到此处的时候,相关的配置信息还没被载入进来,都是空,会有问题。所以我们要提前主动触发(getBeanOfType与getBean逻辑一致,都是先拿,拿不到就实例化再存入三级缓存)PropertyResourceConfigurer的实例化,这样相关的配置就能够被载入进来了。
  10. private void processPropertyPlaceHolders() {
  11. // 先主动触发该类型的Bean的实例化。
  12. Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
  13. if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
  14. BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext)
  15. .getBeanFactory().getBeanDefinition(beanName);
  16. // PropertyResourceConfigurer does not expose any methods to explicitly perform
  17. // property placeholder substitution. Instead, create a BeanFactory that just
  18. // contains this mapper scanner and post process the factory.
  19. DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
  20. factory.registerBeanDefinition(beanName, mapperScannerBean);
  21. for (PropertyResourceConfigurer prc : prcs.values()) {
  22. prc.postProcessBeanFactory(factory);
  23. }
  24. PropertyValues values = mapperScannerBean.getPropertyValues();
  25. // 更新相关重要字段信息
  26. this.basePackage = updatePropertyValue("basePackage", values);
  27. this.sqlSessionFactoryBeanName = updatePropertyValue("sqlSessionFactoryBeanName", values);
  28. this.sqlSessionTemplateBeanName = updatePropertyValue("sqlSessionTemplateBeanName", values);
  29. }
  30. }

回到ClassPathMapperScanner#scan(),该方法内部会继续调用doScan()方法。

  1. /**
  2. * Calls the parent search that will search and register all the candidates.
  3. * Then the registered objects are post processed to set them as
  4. * MapperFactoryBeans
  5. */
  6. @Override
  7. public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  8. //先调用父类ClassPathBeanDefinitionScanner#doScan()方法
  9. //扫描并将Bean信息整合成BeanDefinition注册进Spring容器,且包装成BeanDefinitionHolder返回
  10. Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
  11. if (beanDefinitions.isEmpty()) {
  12. logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
  13. } else {
  14. // 重要!!! 将BeanDefinition的定义适配成MapperFactoryBean。(当前BeanDefinition的beanClass是Mapper Interface,是无法实例化的。)
  15. processBeanDefinitions(beanDefinitions);
  16. }
  17. return beanDefinitions;
  18. }

如下是processBeanDefinitions()的核心代码片段

  1. private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  2. GenericBeanDefinition definition;
  3. for (BeanDefinitionHolder holder : beanDefinitions) {
  4. definition = (GenericBeanDefinition) holder.getBeanDefinition();
  5. // 省略代码
  6. ......
  7. // the mapper interface is the original class of the bean
  8. // but, the actual class of the bean is MapperFactoryBean
  9. // 添加构造函数参数值,当前Mapper的Class。
  10. definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
  11. // 将Bean的类型定义修改为MapperFactoryBean,这样实例化出来的就是一个MapperFactoryBean了
  12. definition.setBeanClass(this.mapperFactoryBean.getClass());
  13. definition.getPropertyValues().add("addToConfig", this.addToConfig);
  14. // 省略代码
  15. ......
  16. }
  17. }

这段代码非常关键!首先,它对Bean的构造函数参数值做了干预,将当前的BeanClassName设置进去了(如UserMapper.class),而从第二行代码中也能知道该Bean的Class被修改成了MapperFactoryBean,所以我们去看看MapperFactoryBean的构造函数就行了。

  1. public MapperFactoryBean(Class<T> mapperInterface) {
  2. // Mapper Interface。(如UserMapper.class)
  3. this.mapperInterface = mapperInterface;
  4. }
  5. /**
  6. * {@inheritDoc}
  7. */
  8. @Override
  9. public T getObject() throws Exception {
  10. return getSqlSession().getMapper(this.mapperInterface); // 等同于如:sqlSession.getMapper(UserMapper.class)
  11. }

其次它将Bean的实例化类型从无法实例化的Mapper Interface修改成了可以实例化的MapperFactoryBean类型。

以上操作后,就将一个Mapper Bean包装成了MapperFactoryBean,而交给Spring管理的是Mapper对应的代理实例(通过mapperInterface字段绑定关系),所以我们就能通过@Autowired将Mapper(MapperFactoryBean#getObject())依赖注入进来直接使用了。

到此,Spring整合MyBatis的内容就结束了。

再顺便聊一下其中的变动-事务管理器,因为Spring整合了MyBatis所以后续的事务就应该由Spring来管理了。

之前在MyBatis中如果配置的是“JDBC”,则是JdbcTransactionFactory

值得一提的是,SpringManagedTransaction 中除了维护事务关联的数据库连接和数据源之外,还维护了一个 isConnectionTransactional 字段(boolean 类型)用来标识当前事务是否由 Spring 的事务管理器管理,这个标识会控制 commit() 方法和rollback() 方法是否真正提交和回滚事务,相关的代码片段如下:

  1. public void commit() throws SQLException {
  2.     if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit){
  3.         // 当事务不由Spring事务管理器管理的时候,会立即提交事务,否则由Spring事务管理器管理事务的提交和回滚
  4.         this.connection.commit();
  5.     }
  6. }

看一眼Xml方式的Spring整合MyBatis,通过两个核心的组件就完成了Spring整合MyBatis。

  1. <!-- 省略 -->
  2. <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  3. <property name="dataSource" ref="dataSource"/>
  4. </bean>
  5. <bean id="scannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  6. <!-- name="basePackage":(起始)包名, 从这个包开始扫描-->
  7. <property name="basePackage" value="com.deepz.mapper"/>
  8. </bean>
  9. <!-- 省略 -->

总结下本文的内容吧:

  • 回顾了MyBatis的相关概念:SqlSessionFactory、SqlSession、Mapper
  • 回顾了Spring的相关概念:FactoryBean、InitializingBean、BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor
  • 跟进Spring整合MyBatis的两个重要步骤:1. SqlSessionFactory的维护 2. Mapper的维护
  • 最后还提了一下事务管理器的变化,毕竟是被Spring整合了,事务自然也得交给Spring管理

SqlSessionFactory的注入:

SqlSessionFactoryBean中的buildSqlSessionFactory()会读取MyBatis的核心配置载入内存,并构建出SqlSessionFactory通过FactoryBean的getObject()交给Spring管理。

而buildSqlSessionFactory()方法的触发时机有两个:1. 在Bean初始化的时候Spring回调InitializingBean的afterProperties();2. FactoryBean的getObject()方法会前置判断SqlSessionFactory是否为空,是空则会调用。

Mapper的注入:
MapperScannerConfigurer在Spring应用上下文启动的时候,在较早的时机回调BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry()方法

通过ClassPathMapperScanner来扫描指定过滤条件(包路径、注解类型…)的类,包装成BeanDefinition注册进容器。

同时将这些BeanDefinition做“加工”处理,就是我们讲的“processBeanDefinitions()”。它主要做的两件事:1. 添加构造函数参数值,将当前BeanDefinition的Class传递进去,作为后续sqlSession.getMapper();的入参。2. 将BeanDefinition中的beanClass替换成MapperFactoryBean.class,使得Spring通过BeanDefinition实例化出来的是MapperFactoryBean,上演了一出狸猫换太子。最后注入进去的又是getObject()中MyBatis根据MapperFactoryBean中的mapperInterface字段创建的代理对象, 完成了将Mapper交给Spring管理的目标。

原创不易,希望对你有帮助。欢迎多多指导和讨论。

版权声明:本文为deepSleeping原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/deepSleeping/p/15070404.html