MyBatis(十三) - 整合Spring

1. 整合

2. 整合原理

回到 spring-mybatis.xml 配置文件中,MyBatis 整合 SpringFramework 的核心就是那个 **SqlSessionFactoryBean** ,我们的切入点就是从这里出发。

2.1 SqlSessionFactoryBean的设计

先简单看一下 SqlSessionFactoryBean 的内部成员,一上来我们就可以看到两个非常核心的东西:

public class SqlSessionFactoryBean
        implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

    // ......

    private Resource configLocation;

    private Configuration configuration;

嚯,MyBatis 的配置文件路径,以及 MyBatis 的核心 **Configuration** 对象!有这些东西,想必构造 SqlSessionFactory 也应该不成问题吧。

再往下看,更多的熟悉的东西都会映入我们眼帘:

    private Resource[] mapperLocations;
    private DataSource dataSource;
    private TransactionFactory transactionFactory;
    private Properties configurationProperties;
    private Interceptor[] plugins;
    private TypeHandler<?>[] typeHandlers;
    private String typeHandlersPackage;
    // ......

这些属性都不需要小册一一解释吧,各位都是从前面一路学过来的,这些也都能看得懂。

另外,观察这个 SqlSessionFactoryBean 实现的接口:

  • FactoryBean<SqlSessionFactory> :可以通过 getObject 方法构造 SqlSessionFactory

  • InitializingBean :有初始化逻辑(猜想会不会就是这个初始化的切入点加载了 MyBatis 的相关东西呢?)

  • ApplicationListener<ApplicationEvent> :监听一些事件(实际上它只监听了 ContextRefreshedEvent 事件)

所以接下来的着眼点,我们就可以从这几个接口对应的方法往下看。按照 SpringFramework 对这几个接口的处理,我们先来看 InitializingBean 接口的 afterPropertiesSet 方法。

2.2 afterPropertiesSet

这个 afterPropertiesSet 方法只有一句话,但是这句话至关重要:

构建 **SqlSessionFactory** !!!这动作可太关键了,我们一定要好好看下去。但是这个方法非常非常长,所以小册会拆解成多段来看。

2.2.1 处理MyBatis全局配置对象

首先的这一段,它会先准备一个 MyBatis 的全局配置对象 Configuration ,并根据是否事先注入 configuration 对象或者传入全局配置文件路径,决定是否准备 XMLConfigBuilder 。如果确实需要 XMLConfigBuilder 的处理,在下面会有调用它的 parse 方法,后面我们会看到。

2.2.2 处理内置组件

这三个组件就不用多解释了吧,之前第 7 章也遇见了,这里的设置只是为了考虑到 MyBatis 整合 SpringFramework 时,有些配置是在 <bean> 中配置而没有在 MyBatis 全局配置文件中而进行的分别处理。

2.2.3 别名处理

别名的包扫描,和某些特定类的别名设置,这个也都很简单了,也很容易理解。

默认情况下,包扫描注册的别名就是类名(首字母大写),如果类上有标注 @Alias 注解,则取注解属性值。

2.2.4 处理插件、类型处理器

接下来是处理 MyBatis 插件,以及 TypeHandler ,内容也非常简单,扫一眼即可。

留一个小细节,默认情况下最后一行的那个 defaultEnumTypeHandler 为 null ,除非我们在注册 SqlSessionFactoryBean 时注入过,否则 MyBatis 处理枚举类型时仍会使用默认的处理器。

2.2.5 处理边角组件

接下来的一些组件都是边角性的了,我们平时也不会主动碰它,所以直接忽略就好啦。

2.2.6 解析MyBatis全局配置文件

上面的边边角角东西都处理的差不多了,接下来就是加载全局配置文件了,由于 xmlConfigBuilder 在执行构造器时已经把全局 Configuration 注入进去了,所以这个 parse 动作完事后,配置文件的内容也就都进入 Configuration 中了。

2.2.7 处理数据源和事务工厂

这段话只有一句代码,但这里面包含了两部分:数据源与事务工厂,注意默认情况下 MyBatis 与 SpringFramework 整合之后底层使用的事务工厂不再是 JdbcTransactionFactory ,而是 SpringManagedTransactionFactory ,不过底层的逻辑与原生 jdbc 并无太大差别。

2.2.8 处理Mapper

因为 SqlSessionFactoryBean 只能传入 mapper.xml 的路径,所以这里的处理逻辑只有加载和解析 mapper.xml ,至于 Mapper 接口的话,那是另外的 MapperScannerConfigurer 了,我们马上就说它。

经过这么一大段 afterPropertiesSet 的逻辑,SqlSessionFactory 也就创建出来了。

2.3 事件监听

别忘了,最上面我们在看 SqlSessionFactoryBean 的接口实现中,还实现了一个 ApplicationListener 呢,那对应的 onApplicationEvent 方法我们也应该关注一下:

注意看,它只监听了 ContextRefreshedEvent 类型的事件,那就意味着等 IOC 容器刷新完毕后,这个逻辑就会随之执行,先不看逻辑具体是什么,看一下这行注释:

fail-fast -> check all statements are completed.

快速失败->检查所有语句是否已完成。

所以说这个动作相当于是 MyBatis 初始化完成后的一个收尾检查性质的处理咯。点进去看一下 getMappedStatementNames 方法都干了些啥:

等一下,这个方法是有返回值的,但上面没用到,所以这个动作只是为了触发 buildAllStatements(); 这一行代码咯,那为什么不直接调用它呢?

废话,要不是这个方法设置成 protected 或者 private ,你觉得 MyBatis 不会直接调用它吗?(狗头)

聊回来,我们看看这个 buildAllStatements 方法都干了些什么:

可以发现,之前我们在第 10 章和第 13 章中看到的那些没有处理好的东西,在 IOC 容器刷新完毕后,MyBatis 还是不想放弃它们,想再给它们一次机会,于是就把它们又尝试着处理了一次。当然,这也是初始化阶段的最后一次机会了,这次要是再处理不了,那就拉倒了,真不管了。

到此为止,整个 SqlSessionFactoryBean 的任务也就全部完成了,大概抓住几个要点即可:

  • 几乎可以代替 MyBatis 全局配置文件

  • 可以传入全局配置文件,供 MyBatis 解析和处理

  • 代替 MyBatis 处理数据源和事务工厂

  • 只处理和解析 mapper.xml

SqlSessionFactoryBean 只负责 mapper.xml 的处理,那 Mapper 接口怎么办呢?哎别急,下面还有一个组件呢。

2.4 MapperScannerConfigurer

从类名上也能看得出来,它就是扫描 Mapper 接口的核心了。

2.4.1 类的设计

首先请各位注意一点,这个家伙的本质是一个 BeanDefinitionRegistryPostProcessor

这个细节相当重要,在 Spring 小册的第 28 章中我们有提过它,BeanDefinitionRegistryPostProcessor 的处理阶段最好不要直接使用 getBean 或者相似的方法去获取 IOC 容器中的 Bean ,如果只是为了检查或者依赖相关的 bean 的话,可以只依赖 bean 的 name 。

MyBatis 当然帮我们考虑到这个问题了,于是这个组件的内部设计是这样的:

对应的,setter 方法也做了特殊处理:

可以说是非常贴心了哈,我们在实际用的时候也要注意为好,否则真的会出一些预期之外的问题。

2.4.2 核心处理逻辑

说完了这个要注意的点,我们还是要看核心的处理逻辑,BeanDefinitionRegistryPostProcessor 的核心方法是 postProcessBeanDefinitionRegistry ,这个方法会向 BeanDefinitionRegistry 中注册新的 BeanDefinition ,具体的逻辑我们可以来看看源码逻辑:

很明显,这里面的核心是这个 **ClassPathMapperScanner** ,它会执行包扫描的动作,并且将扫描到的 Mapper 接口都收集起来,构造为一个一个的 MapperFactoryBeanMapperFactoryBean 想必各位都不会觉得陌生吧,它就是适配 Mapper 接口的工厂 Bean )。那么核心的逻辑也就是 ClassPathMapperScanner 的 scan 方法了。

2.4.2.1 scan

很不巧,这个 scan 方法其实是 ClassPathBeanDefinitionScanner 的,因为 ClassPathMapperScanner 继承自 ClassPathBeanDefinitionScanner ,所以会先来到 ClassPathBeanDefinitionScanner 中:

嚯,又是 SpringFramework 的经典套路了,xxx 方法最终调用 doXxx 方法,真正负责干活的是 doXxx 方法。其实读到这里我们应该有一种感觉:doXxx 方法应该是类似于模板方法那样,让子类重写了吧!OK 我们继续往下看。

2.4.2.2 doScan

ClassPathMapperScanner 重写了 doScan 方法,也仅仅是多了一层 BeanDefinition 的处理而已,包扫描的逻辑还是用的 ClassPathBeanDefinitionScanner 的,那我们就不关心具体如何扫描了,主要是看扫描到 Mapper 接口之后都干了什么。

2.4.2.3 processBeanDefinitions

方法体略微有点长,不过没必要拆解开了,小册给源码中关键逻辑都加了注释,所以不用担心看起来费劲:

大体走下来,就这么两大步骤:拿着 Mapper 接口的全限定名创建 MapperFactoryBean ,注入 SqlSessionFactorySqlSessionTemplate

处理好这些 BeanDefinition 之后,其实 BeanDefinitionRegistry 中就有这些 MapperFactoryBean 的定义了,后续的 bean 实例化阶段也就都能创建出对应的代理对象了,程序执行阶段也就可以拿得到了。

到这里,MyBatis 整合 SpringFramework 的两个重要组件的原理剖析也就全部完成了,内容不是很难,不过需要小伙伴们前期熟悉 SpringFramework 中 IOC 的一些高级特性,以便更好地理解。

最后更新于

这有帮助吗?