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.      构建SqlSessionFactory并存储MapperProxyFactory流程

  2.1 整体流程图

  2.2 配置SqlSessionFactoryBean

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

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

  2.3 构建SqlSessionFactoryBean

先来看看SqlSessionFactoryBean类的定义

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

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

  2.4 配置了configuration或configLocations的处理

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

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

再回到构建sqlSessionFactory的过程中

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

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

  2.5 配置了mapperLocations的情况处理

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

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

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

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

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

  2.6 configuration.addMapper

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

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

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

3  在Spring中注册MapperFactoryBean的流程

  3.1 整体流程图

  3.2 配置MapperScannerConfigurer

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

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

  3.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下的所有资源,然后会对资源进行判断,是否满足候选人的条件

  3.4 候选人的条件

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

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

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

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

  3.6 设置候选对象的Bean属性

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

继续来看ClassPathMapperScanner#doScan方法,

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

    3.6.1 设置bean定义的Class类型

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

上图为MapperFactoryBean的构造函数

    3.6.2 设置SqlSessionFactoryBean

并把sqlSessionFactory或SqlSessionTemplate设置进去,该SqlSessionFactoryBean即为上文中初始化的Bean

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

MyBatis-Spring中间件逻辑分析(怎么把Mapper接口注册到Spring中)的更多相关文章

  1. Mybatis是如何将Mapper接口注册到Spring IoC的

    1. 前言 有时候我们需要自行定义一些注解来标记某些特定功能的类并将它们注入Spring IoC容器.比较有代表性的就是Mybatis的Mapper接口.假如有一个新的需求让你也实现类似的功能你该如何 ...

  2. Mybatis学习系列(四)Mapper接口动态代理

    实现原理及规范 Mapper接口动态代理的方式需要手动编写Mapper接口,Mybatis框架将根据接口定义创建接口的动态代理对象,代理对象的方法体实现Mapper接口中定义的方法. 使用Mapper ...

  3. mybatis源码解析8---执行mapper接口方法到执行mapper.xml的sql的过程

    上一篇文章分析到mapper.xml中的sql标签对应的MappedStatement是如何初始化的,而之前也分析了Mapper接口是如何被加载的,那么问题来了,这两个是分别加载的到Configura ...

  4. 【mybatis源码学习】与spring整合Mapper接口执行原理

    一.重要的接口 org.mybatis.spring.mapper.MapperFactoryBean MapperScannerConfigurer会向spring中注册该bean,一个mapper ...

  5. Mybatis Mapper接口是如何找到实现类的-源码分析

    KeyWords: Mybatis 原理,源码,Mybatis Mapper 接口实现类,代理模式,动态代理,Java动态代理,Proxy.newProxyInstance,Mapper 映射,Map ...

  6. mybatis如何根据mapper接口生成其实现类

    SpringBoot集成mybatis mybatis的statement的解析与加载 mybatis如何根据mapper接口生成其实现类 mybatis的mapper返回map结果集 mybatis ...

  7. mybatis如何根据mapper接口生成其实现类(springboot)

    序 mybatis里头给sqlSession指定执行哪条sql的时候,有两种方式,一种是写mapper的xml的namespace+statementId,如下: public Student fin ...

  8. 精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  9. mybatis从mapper接口跳转到相应的xml文件的eclipse插件

    mybatis从mapper接口跳转到相应的xml文件的eclipse插件 前提条件 开发软件 eclipse 使用框架 mybatis 为了方便阅读源码,项目使用mybatis的时候,方便从mapp ...

随机推荐

  1. MySQL数据库存储过程动态表建立(PREPARE)

    PREPARE statement_name FROM sql_text /*定义*/ EXECUTE statement_name [USING variable [,variable...]] / ...

  2. Mina源码阅读笔记(七)—Mina的拦截器FilterChain

    Filter我们很熟悉,在Mina中,filter chain的用法也类似于Servlet的filters,这种拦截器的设计思想能够狠轻松的帮助我们实现对资源的统一处理.我们先大致连接下mina中的f ...

  3. 64位linux下安装ps模拟器ePSxe

    早就想在爱机上玩ps游戏,特别是彩京的1945一代和非常经典的实况足球2002版.在ubuntu64位下可以通过wine模拟的方式运行windows版的ePSxe,但是总觉得差些呢?非原生啊!网上搜了 ...

  4. LeetCode(36)- Implement Stack using Queues

    题目: Implement the following operations of a stack using queues. push(x) -- Push element x onto stack ...

  5. 基于 Java Web 的毕业设计选题管理平台--选题报告与需求规格说明书

    一.选题报告 目录 团队名称 团队成员 项目名称 项目描述 创新与收益 用户场景分析 真实用户调研 未来市场与竞争 项目导图 比例权重 总结 1.团队名称--指南者团队 2.团队成员 孔潭活:2015 ...

  6. Angular v6 正式发布

    Angular 6 正式发布 Angular 6 已经正式发布了!这个主要版本并不关注于底层的框架,更多地关注于工具链,以及使 Angular 在未来更容易快速推进. 作为发布的一部分,我们同步了主要 ...

  7. nginx日志中添加请求的response日志

    换个新公司,做一些新鲜的事情,经过一天的琢磨,终于成功添加response日志 在nginx的日志中添加接口response的日志 由于此功能在nginx内置的功能中没有,需要安装第三方模块ngx_l ...

  8. jQuery插件之----缓冲运动

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  9. VMware虚拟化培训手册

    一.VMware虚拟化架构概述 1.1VMware虚拟化架构图 如上图所示,虚拟化由物理主机(即ESXI主机).虚拟化管理程序(vCenter Server).虚拟机(操作系统).存储等基本组成. 1 ...

  10. Angularjs Post传值后台收不到的原因

    如果你给AngularJS的post方法的data参数创一个key-value对象,那传给后台服务的就是JSON字符串,而正常的POST解析是需要像get?后面的那种&name=value这样 ...