Mybatis source code analysis - spring + mybatis actual combat

 

1. Application layer injection (what are we using when we are using mybatis?)

When we are using mybatis+spring, we actually only inject two classes in the configuration:
1. <bean  id ="sqlSessionFactory"  class ="org.mybatis.spring.SqlSessionFactoryBean" >
2. <bean  class ="org.mybatis.spring.mapper.MapperScannerConfigurer >
 
 
As the name suggests, one of these two classes is the sessionFactoryBean used to generate sqlSession. Factorybean is a bean extension provided by Spring. The factory mode must be familiar to everyone. The sessionFactory itself is used as a Spring bean in spring. This is FactoryBean. Here we manually inject a path in xml: <property name ="configLocation"  value ="classpath:mybatis/mybatis-configuration.xml />
and a datasouce, configlocation is the path to our mabatis configuration file
 
There is also the MapperScaner's Configurer used to scan the mybatis configuration file. There are only some configuration items that are manually injected, such as the package name, SessionFeactoryBeanName. The real scanning work is done by a ClassPathMapperScanner. In this class, we manually configure two Attributes:
<property  name ="basePackage"  value ="com.vdian.feedcenter.admin.core.dao" />
<property  name ="sqlSessionFactoryBeanName"  value ="sqlSessionFactory" />
One is the package name of the dao layer, and the other is the sessionFactoryBean mentioned earlier.
So what exactly are these two classes?
First look  at org.mybatis.spring.SqlSessionFactoryBean
 
 


 
Let's first look at the interface of this class (due to java's single inheritance, when we cannot fully confirm that the relationship between classes is an is-a relationship, we often use interfaces to implement extensions, while inheritance uses Be cautious, this is especially evident in the spring framework)
1.FactoryBean  
An extension interface provided by spring to us
Used to manage various BeanFactory When we use getBean('the ID of a factoryBean') in spring, the bean we get is actually the bean (factory) returned by the getObject() method in the factoryBean, not the factoryBean itself
2.InitiallizingBean 
When spring encounters a class that implements this interface, it will call a method for initialization after all properties are injected: afterPropertiesSet(), we generally write the initialization of beans in this method
3.ApplicationListenner
Listen to the spring refresh event of spring, which is used to update the internal information of the class when the spring refreshes
 
Let's look at the methods of this class:
Apart from the get set method of a bunch of properties, there are only a few methods we need to pay attention to
1. The initialization method of the afterPropertiesSet   object: After confirming that the two key properties are not empty, call the buildSqlSessionFactory method
2.  buildSqlSessionFactory    
As the name suggests, this method builds a SqlSessionFactory and probably uses the builder pattern. This method has more than 100 lines, and in general it does several things,
2.1. Configure an XMLConfigBuilder . There is a parse method in this builder, which is the specific implementation of parsing the mybatis configuration file. The parse method returns a Configuration     object, which contains everything we wrote in the configured xml. The specific analysis process will be discussed later.
2.2.  XMLConfigBuilder 这个对象在构造函数里面会对 自己本身的 Configuration 对象属性,进行一部分初始化工作,例如validate,但不会进行具体解析,在 buildSqlSessionFactory 这个方法里面,我们拿到了这个初步初始化之后的 Configuration 对象,进行了进一步的初始化,例如我们在spring里面配置的 插件 ,alias ,还有objectFactory ,transactionFactory
2.3.最后,调用 XMLConfigBuilder 里面的parse方法,正式解析xml文件。
 
 
然后我们再看一下 MapperScannerConfigurer


 
还是先看接口:
这里除了InitializingBean 之外还有两个aware 和 一个processor接口:
aware接口:感知接口,applicationContextAware :感知这个Bean所处的Spring的Context(上下文)
beanNameAware :感知这个Bean在Spring容器中的beanName
BeanDefinitionRegistryProcessor:
这里说一下postprocessor : spring 给我们预留的processor接口允许我们在spring 处理初始化逻辑的时候,允许程序员处理一些与spring框架本身初始化相关的事情,而不是Bean本身的初始化。
例如BeanDefinitionRegistryProcessor,这个接口就允许我门在初始化Bean本身(afterpropertiesset)之后,对spring框架本身所维护的BeanDefinitionRegistry做一些修改,至于BeanDefinitiionRegistry是什么 ,再之后的内容中会有解释。
然后再来看看方法:
  postProcessBeanDefinitionRegistry:整个类里面只有这一个重要的方法 他是 实现了BeanDefinitionRegistryProcessor 接口必须实现的方法:它使得我门可以修改spring所维护的一个 BeanDefinitionRegistry ,它是spring框架中非常重要的一个接口。
接口文档上是这么说的:
Interface for registries that hold bean definitions, for example RootBeanDefinitionand ChildBeanDefinition instances. Typically implemented by BeanFactories thatinternally work with the AbstractBeanDefinition hierarchy.
这个接口维护了一些个register(注册表?),这个注册表持有许多BeanDefinition(类的定义),例如根类与子类的定义,这个接口一般被抽象层级的BeanFactory所实现。
好像有点不知所云 ,我打个断点来看看:、


 
我们可以看到在实际上这个接口被DefaultListableBeanFactory实现,那这里的DefaultListableBeanFactory又是什么东西?
DefaultListableBeanFactory 是整个spring ioc的始祖 ,
当我们使用原生spring 我们会得到一个applicationContext,当我们想从Spring得到一个对象,我们会调用它的getBean 方法,那这个getBean是怎么实现的? 答案就是DefaultListableBeanFactory。
这个DefaultListableBeanFactory 就是我们所使用的所有的Spring Bean 的工厂类(也可以说是容器),它实现了 BeanDefinitionRegistry这个接口。
 
 
 
    mybatis在 postProcessBeanDefinitionRegistry 方法里面拿到了 BeanDefinitionRegistry
 
之后往里面插入了一些BeanDefinition ,先是new 了一个 ClassPathMapperScanner 这个对象的作用是用来扫描base package(也就是我们doScan在 MapperScannerConfigurer配置的basepackage属性包路径下所有的类和接口(一般是各种DAO),然后解析成BeanDefinition,然后插入 BeanDefinitionRegistry 。
具体实现在 ClassPathMapperScanner doScan()方法里面实现。 具体实现会在之后讲到。
 
二.核心层(mybatis是怎样工作的)
1.binding and mapper 绑定与映射 (当我们启动应用时,mybatis干了些什么)
mybatis框架的绑定与映射主要是将我们写的dao层的方法与配置文件xml中的sql语句相互映射。首先,他们是怎样被加载到我们的应用里面去的呢?
1.1 xml配置文件的解析
之前在介绍 SqlSessionFactoryBean的时候就提到过 xmlConfigBuilder 里面的parse方法是用来解析我们写的xml配置文件的,实际上在parse方法里面所调用 XPathParser的把xml文件解析成一个 XNode,的然后将这个XNode传入 parseConfiguration 方法。 我们知道Xpath是w3c的一个标准,实际上在这个 XPathParser 里面,也是调用的org.w3c.dom,和org.xml.sax 两个开源包来解析xml文件,具体实现这里略过,只看大概逻辑,如下:
 
 
 
平铺易懂的代码风格,每一行解析了一个子模块,所有的方法都没有返回值,解析结果统一储存在了这个对象的configuration,typeAliasRegistry ,typehandlerREgistey三个属性里面,最重要的mapper(类,方法的映射信息)被储存在configuration,来看一下configuration 这个属性对象,这是一个非常复杂的大对象(几十个方法与属性)。暂时只介绍mapper相关的方法与属性
1.方法: addMapper (Class< T type
parser从配置文件中解析出来一个接口,将其录入configuration
2.方法:   getMapper (Class< T > type SqlSession sqlSession) 
在spring 注入Bean的时候,需要通过这个方法得到一个实现了所需接口的Bean
3.属性: MapperRegistry  mapperRegistry
 
configuration维护了一个 MapperRegistry属性, addMapper 属性与 getMapper 都是通过这个属性实现,来看一下 MapperRegistry
两个属性:
Configuration config :Configration 与MapperRegistry 相互持有,在addMapper 的时候需要用到configration
Map<> knowmappers :map<接口,接口的工厂对象> 核心属性,维护了一个 接口 与 用于生产实现接口的 代理对象的  工厂对象。这里是一个典型的工厂模式
 



 

 
MapperProxyFactory如下


 
既然是工厂类,那么作用就只有一个,那就是产生对象,具体来讲就是产生实现了具体接口(比图XXXDAO)的代理对象。具体生产如下


 
首先会根据上层传过来的sqlSession生成一个 MapperProxy ,这个 MapperProxy 实现了 InvocationHandler  , java.lang. reflect 这个包下面有一个Proxy类与一个 InvocationHandler 接口,调用Proxy的 newProxyInstance 可以得到一个指定接口的代理类,接口里面的方法由 InvocationHandler 拦截,调用 InvocationHandler 里面的 invoke
,使用反射(invoke)的方式调用方法的具体实现,在 MapperProxy这里mybatis框架做了一层方法的缓存:


 
至此,一个实现了指定接口(DAO)的Instance 就被生成。
那么mybatis是怎样将这个Instance注入到我们所写的业务Bean里面去的呢? 
回到之前说的 ClassPathMapperScanner ,我们看一下它的doScan方法


 
首先这个函数调用了super.doScan()  ClassPathMapperScanner 继承自 ClassPathBeanDefinitionScanner 这个类是Spring 里面默认的ClassPathScaner  spring用它扫描目的路径(包)里面所有的带Component,Service,Controller注解的class文件,并解析成相应BeanDefinition ,这个类的doScan返回了一个Set<BeanDefinitionHolder>,这就是扫描结果。
ClassPathMapperScanner 里面的doScan拿到扫描结果BeanDefinition之后,干了两件事情:
       首先为BeanDefinition设置 了四个属性  addToConfigmarkerInterfacesqlSessionTemplatesqlSessionFactory (之后会用到),然后修改BeanDefinition 的BeanClass,改为MapperFactoryBean,这才是将MapperProxy注入业务代码的关键部分,当Spring 注入一个Instance时,会根据BeanClass调用ClassLoader 将Class加载到系统,所以实际上我们注入一个DAO的时候,会注入一个MapperFactoryBean(然而实际上我们调用的是 MapperProxy),我们来看一下MapperfactoryBean
 
 


 
这个类继承自SqlSessionDaoSupport 同时实现了FactoryBean   ,
FactoryBean之前说过,在spring里面将注入getObject里面所返回的对象,直接看getObject方法即可。


 
 
之前说过在 BeanDefinition里面设置了一个属性 sqlSessionTemplate ,这里在getSqlSession里面被拿出来了,然后调用了getMapper方法。


 
而getMapper方法是调用了configuration里面的getMapper方法,而这一个configuration就是在本节开头提到的configuration 属性,它最开始出现在SqlSessionFactory里面,而SqlSessionFactory是一个单列,他里面这个属性被很多对象引用。而这里所返回的Mapper,就是之前所说过的MapperProxyFactory 生产的MapperProxy。自此,当我们启动应用的时候,mybatis所做的初始化工作就完成了。
 
2.invoke and excute 反射与执行 (当我们的业务调用DAO层的时候,mybatis干了什么)
     我们业务代码里面所注入的DAO层的Bean实际上是一个MapperProxy,它代理了所有的Dao层的方法,我们来看一下mapperProxy的invoke方法


 
这里可以看到先对上层传入的方法做了判断,如果DeclaringClass(声明这个方法的类)是Object ,就调用本类的方法,如果不是,就去Proxy里面维护的一个 methodCache 里面拿一个 MapperMethod 类,没有的的话就新建一个(总而言之就是一个缓存),然后调用 MapperMethod MapperMethod里面的excute方法。来看一下MapperMethod 类:这个类没有实现任何接口,父类是Object  ,所以先看属性和构造方法
 
这里定义了两个final属性,在构造函数里面也主要是构造这两个final属性。先看SqlCommand这个类,它是类MapperMethod的内部类


 
顾名思义,sqlCommand是一个sql命令的封装,但我们的sql是写在xml配置文件里面的所以这个类的构造函数干了这么几件事:首先通过传入的Method找到sqlStatement的名字(接口名.方法名),去configration里面找对应方法的statement(所有mapper的xml配置文件里面的内容都被解析到了configration里面),然后处理Statement的继承关系,最后将Statement的id 和type记录在对象的属性里面,供以后调用Statement。
然后再看一下 MethodSignature ,这个类也是一个内部类


 
从构造函数看出,这里面储存了对于DAO接口所定义的方法的一些描述,例如返回值 类型 参数数量之类
 
最后看一下最重要的excute方法:
 
在sqlcommand里面初始化了一个command type,描述了
sql语句的类型,整理根据sql的类型(增删改查)和返回值调用了sqlSession里面的insert,delete,update,select 方法,这里的sqlSession是一个接口,mybatis实际上默认是用SqlSessionTemplate 实现了这个接口(在mapperProxyFactory里面),来看一下增删改查的具体实现类SqlSessionTemplate:
他有四个 final属性:


 
其中sqlSessionProxy非常重要,因为所有增删改查都是通过调用它实现的,以selectOne为例:
 
既然sqlSqssionTemplate本身实现了sqlSession 接口,为什么要加一层sqlSessionProxy 呢?来看sqlSessionProxy的拦截器里面:


 
 
答案是事务管理,在invoke里面将不同的dao层的方法路由到不同的sqlSession对象,方便事务的回滚,所以需要一个代理,这里调用了SqlSessionUtils.getSqlSession,里面根据上层传入的executorType与Spring自带的TransactionManager 返回正确的SqlSession。然后再调用session的增删改查。这里默认的sqlSession实现类是DefaultSqlSession(由DefaultSqlSessionFactory生成,这个工厂类就是我们在xml配置的 SqlSessionFactoryBean 生成的 ),来看看这个类


 
这个才是mybatis增删改查数据看的SqlSession的具体实现,以select为例:


 
参数解释:statement: 调用的包名+类名+方法名
parameter:DAO层的参数
roundBounds:选择边界
这个方法首先通过在configuration里面拿到statement对应的MappedStatement,MappedStatement 就是我们为每个dao的接口的方法写的一个xml文件下的内容(例如sql语句 返回类型的映射),在初始化应用的时候这些xml被解析,放入configuration ,然后把MappedStatement和DAO层参数传入executor(mybatis里面默认使用CachingExecutor,在sessionFactory里面设置),调用query方法执行。现在我们深入CachingExecutor的query方法:


 
既然名字叫CachingExecutor,那在查询的时候肯定有一层缓存,这个缓存是内存缓存,独立的存在于每个MappedStatement,在缓存不命中的情况会调用delegate属性(一个SimpleExecutor)的query方法:


 
我们可以看到这里又有一层内存级别cache ,这层cache独立存在于Executor(同一批次query共用此chache,查询完毕后马上清除)在不命中的情况下才会调用queryFromDataBase。
 
在queryFromDataBase里面,调用doQuery查询数据库,并且将结果存入localcache,SimpleExecutor重写了doQuery方法:


 
 
这将MappedStatement转换成一个 RoutingStatementHandler 然后调用了里面的query方法,这个Handler的主要作用是根据Statement的类型调用不同的StatementHandler代理:
 
这里的statement是PREPARED类型的。所以我们得到的是一个PreparedStatementhandler。
在doQuery里面,在调用handler执行Statement之前,还会prepareStatement
 
这里会调用PrepareStatementhandler根据Connection的类型对Statement进行预处理这里的Connection与我们所使用的DataSource相关,例如我们这里使用的是一个DistributedConnection ,那么这里预处理之后的Statement就是一个DistributedPreparedStatement。反之,如果这里用的是mysql,那就回返回一个mysql包下面的Statement。
在handler与statement都准备完毕之后,就会执行query了:
 
 
先将statement强转为preparedStatement ,然后调用statement里面的execute方法(与数据库的具体类型相关),最后处理结果集。与数据库类型相关的部分略过,这里看一下结果集的处理部分:DefaultResultSetHandler,实现了ResultHandler接口:
 
在这里我们关注handleResultsSets方法的实现。
构造函数如下:


 
可以看到这类可以拿到configuration (全局单例),executor (全局单例)和mappedStatement等等对象,
结果集处理逻辑如下:
 
首先拿到mappedStatement里面的result maps,那里面储存了我们在xml配置文件里面定义的Dao层方法的返回集的类名,遍历结果多个结果集,根据每一个结果集的resultmap将每一个结果集转化为xml配置的结果对象。单个结果集转换逻辑如下:


 
这里会先处理结果集映射的父类映射,然后调用handleRowValues 处理多行结果:
每一行结果都会分两种处理方式:关联处理和简单处理,关联处理表示本次查询拿到的结果集可能从多个表中读取,需要解析它们之间的关系并整合成一个完整对象,简单处理表示查询结果从一个表或者视图中得到,只需要进行基本类型(元数据)的映射。
这里我们看一下简单处理:
 
做了四件事情:1.新建一个resultContext
2.去除没用的行
3.遍历所有行,每行生成一个rowValue,
4.存储结果(联合查询:将结果与其父结果链接 非联合查询:直接返回结果  )
然后看一下每一行的结果是如何生成的:


 
首先根据resultMap 与resultset等,生成了一个空的resultObject,这个resultObject的类型就是Dao层接口所定义的返回值类型, DefaultResultSetHandler 本身持有一个objectFactory,这个objectFactory可以根据ClassType调用类的构造函数产生新建对象,resultMap就存有result的ClassType。
得到空的resultObject之后,将其包装成一个metaObject,然后对MetaObject
进行属性值的映射:


 
这里遍历了resultMap(我们配置在xml文件里面的结果集映射关系),从resultSet中取出相应的值,并转化成对象。调用metaObject的setValue 将属性的值设置到结果对象内。
至此,一个结果对象装配完成,之后将返回上层业务代码进行处理
 
 
三.总结
mybatis初始化流程如下:


 
 
mybatis执行期间流程如下:


 

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=327105329&siteId=291194637