MyBatis-Spring中间件逻辑分析(怎么把Mapper接口注册到Spring中)

1.      文档介绍

  1.1.      为什么要写这个文档

接触Spring和MyBatis也挺久的了,但是一直还停留在使用的层面上,导致很多时候光知道怎么用,而不知道其具体原理,这样就很难做一些针对性的优化工作,Spring和MyBatis都已经是很庞大的框架了,分析起来会需要很多的时间,所以我先从两者之间的中间件MyBatis-Spring开始,一步一步开始学习两个框架的原理和精髓

  1.2.      MyBatis-Spring是什么

当我们在使用MyBatis时,一般是编写一个Mapper接口和一个Mapper.xml文件,我们都知道接口是不能直接被实例化的,然而我们一般在service层中编写的注入属性都是Mapper接口,那么Spring是如何对该接口进行实例化的呢?

一般而言,如果我们使用Spring和MyBatis作为我们的开发框架时,在搭建开发环境的时候,都会做一个Spring与MyBatis的整合,使用到的就是MyBatis-Spring这个中间件,MyBatis-Spring中间件帮我们把mapper接口和mapper.xml文件对应的代理类注册到Spring中,因此,我们在service层中就能根据类型注入,将对应mapper接口的代理类注入到service层中,我们才能够调用到对应的方法

  1.3 整体原理

在Spring开发中,我们通常是在service层中通过依赖注入Dao层的实例,在MyBatis中,Mapper接口即对应着Dao实例,MyBatis-Spring中间件就是把MyBatis中的mapper.xml和mapper.java对应的Mapper接口注册到Spring容器中,使得service层可以直接通过以来注入获取到Mapper接口

    1.3.1 注册

在Spring中所有的Mapper接口都会被注册为MapperFactoryBean,所有的MapperFactoryBean会共享一个SqlSessionFactory,该SqlSessionFactory由SqlSessionFactoryBean创建,而在sqlSessionFactory的configuration属性中存的是一个Configuration对象,configuratiao对象中的mapperRegistry属性中存储了一个MapperRegistry对象,MapperRegistry对象中的knownMappers属性是一个key为mapper.java文件对应接口的类型,value为MapperProxyFactory的对象。

    1.3.2 获取

当从Spring中获取Mapper接口时,将会调用对应的MapperFactoryBean的getObjects方法,该方法返回值即为对应的MapperProxyFactory创建的MapperProxy动态代理

2  扫描需要注册到Spring中的Mapper接口

   在Spring中注册MapperFactoryBean的流程

  2.1 整体流程图

 

  2.2 配置MapperScannerConfigurer

还是来看一下在文档最开头Spring整合MyBatis时的配置

 

在这里,我只配置了basePackage和sqlSessionFactoryBeanName两个属性,还有一些其他配置,这里我们就不一一解析了,只分析注册MapperFactoryBean最主要的流程。

  2.3 扫描basePackage下的所有候选对象

先来看看MapperScannerConfigurer类的定义

 

首先实现的是postProcessBeanDefinitionRegistry接口,实现了这个接口的类会再Spring初始化Bean定义的时候被调用postProcessBeanDefinitionRegistry方法,用来自定义注册Bean的定义逻辑,先来看看这个方法做了些什么事情

 

在一开始先是调用了本类中的processPropertyPlaceHolders方法,用来设置自己本身的Bean属性,接下来定义ClassPathMapperScanner即Mapper扫描器来扫描对应的Mapper文件,该扫描器继承了Spring中的ClassPathBeanDefinitionScanner,该扫描器是扫描需要实例化的Bean并把它们加载到BeanDefinitionHolder集合中,以便后续的初始化Bean,这里我们直接来看其中的扫描逻辑

 

这里先调用了父类的doScan方法,我们需要先看看父类的doScan方法做了什么事情,

 

核心方法即findCandidateCompents,即从指定的包中找到需要初始化的候选人(即需要初始化的Bean)的定义并把他们放进Set<BeandDefinitionHolder>中,

这两个方法都是在获取候选者,上面的是当设置了ResourceLoader时的调用,其实两个方法在本质上是一样的,我们直接来看下面这个方法

 

可以看到,在寻找候选人的过程中,直接获取对应basePackage下的所有资源,然后会对资源进行判断,是否满足候选人的条件

  2.4 候选人的条件

上节中提到了对候选人进行筛选的方法,idCandilateComponent这个方法实际上是由ClassPathMapperScanner覆盖的,因此,当调用这个方法时,将会执行ClassPathMapperScanner#isCandidateComponent方法

 

即对应扫描的应该为basePackage下独立的接口(即非内部接口),至此,找到了需要实例化的接口,

  2.5 将所有候选对象定义为Spring中的Bean

再获取完候选对象之后,即父类的doScan扫描完毕之后,Spring会将所有满足条件的对象存储到beanDefinition,即Spring中的bean定义对象,这也是Spring初始化Bean的基础,

  2.6 设置候选对象的Bean属性

在Spring中获取到了这些Bean的定义之后,MyBatis-Spring中间件还需要对这些Bean做一些属性上的设置,让其能满足使用的条件,我们接下来看看都有哪些属性的配置

继续来看ClassPathMapperScanner#doScan方法,

 

ClassPathMapperScanner#processBeanDefinitions就是在Spring处理完Bean定义后由MyBatis-Spring来处理的逻辑

 

    2.6.1 设置bean定义的Class类型

上图中最重要的逻辑即先设置构造函数参数为原本读取的BeanDefinition中的类名(即Mapper接口的名称),把所有的Mapper接口定义BeanClass类型设置为MapperFactoryBean,并设置其构造器参数为对应的Mapper接口类型

 

上图为MapperFactoryBean的构造函数

    2.6.2 设置SqlSessionFactoryBean

在processBeanDefinitions()方法中还有一段比较重要的逻辑

在这里,是需要配置MapperFactoryBean的父类SqlSessionDaoSupport中的sqlSessionTemplate属性,当配置的时sqlSessionFactory属性时,MapperFactoryBean的在初始化时,会使用sqlSessionFactory属性来构建sqlSessionTemplate

 

从2.6节可知,设置到MapperFactoryBean中sqlSessionFactory或者是sqlSessionTemplate都是从spring中获取的,这就意味着,

当设置的是sqlSessionFactory时,每个MapperFactoryBean中的sqlSessionTemplate是不同的,但是最终的sqlSessionFactory是相同的,

当设置的是sqlSessionTemplate时,每个MapperFactoryBean中sqlSessionTemplate就是同一个

3.  SqlSessionFactory初始化    

  构建SqlSessionFactory并存储MapperProxyFactory流程

  3.1 整体流程图

  3.2 配置SqlSessionFactoryBean

首先看段Spring整合MyBatis时的经典配置

 

在Spring使用MyBatis-Spring中间件来整合MyBatis必须配置这两个Bean,首先我们从sqlSessionFactoryBean开始,SqlSessionFactoryBean,见名思意,实际上是一个factoryBean,即用来生产对象的工厂Bean,SqlSessionFactoryBean是用来创建上文中提到的SqlSessionFactory的

  3.3 构建SqlSessionFactoryBean

先来看看SqlSessionFactoryBean类的定义

 

实现了InitializingBean的类会在Spring初始化完该Bean后的时候会执行afterPropertiesSet方法,我们来看看这个方法里做了写什么

 

先做了一些数据校验,即必须配置dataSource,sqlSessionFactoryBuilder会在创建对象的时候初始化,不用关心,而对于configuration或configLocation而言,两者不能同时配置,只能配置其中一个,或者一个都不配置,这样在构建sqlSessionFactory时就会采用默认配置,然后开始构建sqlSessionFactory实例

  3.4 配置了configuration或configLocations的处理

在这个方法中,首先会判断是否SqlSessionFactoryBean的Spring配置里针对是否配置了configuration属性或configLocation属性进行针对处理,没有的话就新建一个新的configuration即采用默认配置,接下来,我们先对配置了configLocation的情况进行一次分析,先来看下MyBatis-configuration.xml的内容,先来看一段经典配置

 

在这个配置里面,我们配置了一个Mapper接口,其接口类型为UserMapper,

再回到构建sqlSessionFactory的过程中

在该方法中,使用XMLconfigBuilder初始化了对改配置文件的读取,然后对该配置进行了解析,我们先进去看看解析的过程

 

这里我们跳过对其他配置的解析,直接来看对<mappers>节点的解析,对<mappers>节点下的所有<mapper>节点进行循环遍历,并调用了configuration#addMapper方法

  3.5 配置了mapperLocations的情况处理

我们再来看下配置了mapperLocations的情况

当我们配置了mapperLocations后,在buildSqlSessionFactory中也会对这一属性进行处理

 

这里循环遍历mapperLocations的配置,并使用XMLMapperBuilder来进行读取并解析,我们直接来看对应解析过程

 

获取到的<mapper>节点就是mapperLocation下所有的mapper.xml文件下的mapper节点,再对这些文件进行初始化过程,这里我们也只看注册对应Mapper接口的逻辑

 

跟之前一样,这里也是使用了configuration.addMapper方法,

  3.6 configuration.addMapper

上文中提到了无论是配置了configuration或configLocation还是配置了mapperLocations,都会调用Configuration#addMapper方法,下面我们就来看下这个方法,下图为Configuration#addMapper方法

 

这里委托给mapperRegistry来添加对应的代理,再进去MapperRegistry#addMapper方法,这个方法的工作就是在初始化mapper接口对应的动态代理类了

 

先校验此Mapper的类型是否是接口类型,因为只有MyBatis只处理接口,再校验是否已存在此mapper,可以看到这里初始化了一个MapperProxyFactory并放到已知的Mapper类型Map中,这个MapperProxyFactory即Mapper动态代理的工厂,使用工厂模式来创建Mapper动态代理

4  MapperFactoryBean初始化

  4.1 整体流程图

 

 

  4.2  afterPropertiesSet

在设置完MapperFactoryBean的beanDefinition信息之后,Spring在初始化Bean时就会初始化这个Bean,再来看看这个Bean的定义

 

MapperFactoryBean继承了SqlSessionDaoSupport,而SqlSessionDaoSupport又继承了DaoSupport类,DaoSupport类又实现了InitializingBean接口,意味着当MapperFactoryBean初始化完毕后会调用DaoSupport的afterPropertiesSet方法

来看DaoSupport类中的afterPropertiesSet方法

 

这里调用了checkDaoConfig方法,最终会调用到MapperFactoryBean中的checkDaoConfig方法

  4.3 checkDaoConfig

接下来来看MapperFactoryBean的checkDaoConfig方法

 

可以看到,在MapperFactoryBean属性设置完毕之后,会调用这个checkDaoConfig来检查mapperRegistry中是否存在对应的MapperProxyFactory,如果没有,将会把对应的MapperProxyFactory添加进去,

  4.4 解决的问题

不知道看过上文的读者发现没有,如果在SqlSessionFactoryBean中未配置configuration,configLocation和mapperLocations时,在MyBatis-Spring初始化的第一步完成后(即章节3.1中所提及的内容),SqlSessionFactory中的mapperRegistry中是没有与章节3.2中所注册的Mapper接口的对应关系的,那么,本节中的内容,就是确保mapperRegistry中一定存在两者之间的对应关系,这也是从Spring中获取到Mapper接口的基础

5 从Spring中获取Mapper接口

  5.1  整体流程图

 

  5.2 从Spring中获取Bean的流程介绍

在上一节中,我们扫描到的Mapper接口在Spring中都设置成了MapperFactoryBean,那这么设置有什么用呢?

这里就需要对从Spring中获取Bean有一些了解了,这里先简单介绍一下从Spring中获取Bean的流程

    5.2.1 从Spring中获取Bean的流程图

 

    5.2.2 从Spring中获取Bean具体逻辑

从Spring获取Bean时,都是从BeanFactory即Bean工厂中构建并获取,我们先来看下BeanFactory的源码,当获取对应的Bean时,会调用对应的BeanFactory#getBean方法

 

里面有几个getBean方法的重载

以及判断Singleton或Prototype的方法,

我们直接看它的默认实现AbstractBeanFactory#getBean方法

 

这里我们直接以单例模式举例了,该方法主要是判断需要的Bean是Singleton还是prototype的,然后调用对应的方法,我们这里直接看单例模式下的获取Bean

 

这里我们可以看到,对于Bean的类型有一个区别判断,如果是普通Bean的话会直接返回实例,而对于FactoryBean而言,会执行另外一段逻辑

 

 

其实不用管其他的一些判断逻辑,里面的核心逻辑就一个,调用对应的FactoryBean的getObject方法,

  5.3 MapperFactoryBean#getObject()

那对于我们的MapperFactoryBean而言会怎样呢?

 

很明显,MapperFactoryBean实际上是一个FactoryBean,那我们再进去看看它获取Bean的逻辑

对于MapperFactoryBean即为调用其getObjeact方法,而在MapperFactoryBean对该方法有一个重写

 

在这个方法中,getSqlSession为其父类的方法,

 

在sqlSessionDaoSupport中

 

这个sqlSessionTemplate即为在doScan中设置Beandifinition时设置的,

然后再调用SqlSessionTemplate中的getMapper方法

 

再进去SqlSessionTemplate的getConfiguration方法,实际上获取的是sqlSessionFactory的configuration,

 

 

  5.4 Configuration#getMapper

然后再调用Configuration#getMapper方法

 

可以看到,实际上还是转交给mapperRegistry中的getMapper方法

  5.5 MapperRegistry#getMapper

 

这里我们可以看到,最终是从章节2,章节3中存储的knownMappers中获取到对应Mapper接口的MapperProxyFactory,并最终调用到MapperProxyFactory#newInstance方法获取到对应的MapperProxy

 

这里就是使用newInstance来构建一个新的MapperProxy,即Mapper接口的动态代理类,这里使用的是java自带的动态代理,这里的泛型T就是对应的Mapper接口的类型

6 总结及验证

  6.1 总结

从上面的分析中可以看出,由SqlSessionFactoryBean来扫描mapper接口并配置对应的MapperProxyFactory到mapperRegistry中的knownMappers Map中,由MapperScannerConfigurer来扫描对应的Mapper接口并生成对应的MapperFactoryBean,从Spring中获取mapper接口动态代理类时会调用MapperFactoryBean的getObjects方法,并最终调用到mapperRegistry中的getMapper方法调用mapperProxyFactory的newInstance生成对应的MapperProxy即Mapper接口的动态代理,

可以看到,这里就调用了MapperProxyFactory的newInstance方法获取到对应Mapper接口的动态代理类了,至此,我们基本完成了将Mapper接口注册到Spring的过程

  6.2 验证

接下来我们来看一个简单的验证

Spring配置如下

 

对应的markerInterface如下,如果设置了该属性,只有继承了该接口的Mapper接口会被注册

 

FooMapper接口如下

 

测试类如下

 

其对应的运行结果为

 

即我们获取到的fooMapper实际上是一个动态代理,其在Spring中的类型为FooMapper接口类型,创建它的工厂Bean为MapperFactoryBean类型,与我们分析源代码的结果是一致的

至此,我们初步分析了MyBatis-Spring中间件的原理,对于Mapper接口是如何注册到Spring中的原理有了一个不错的了解

7 写在最后

任何问题请联系hei12138@outlook.com

 

posted @ 2018-05-21 16:29  嘿123  阅读(9147)  评论(5编辑  收藏  举报