使用spring mvc +Mybatis的已经很多年了,通常使用mybatis做为持久层,在spring使用mybatis只需要通过以下简单的四个步骤,就可以从数据库中获取数据。这四个步骤就是:1、定义实体bean ;2、定义mapper接口 ;3、在xml文件中写sql;4、在service中调用相应Mapper方法。突然有一天我的脑海中出现几个疑问:
- 接口是不能被实例化,那么在service中定义的mapper如何被实例化的。
- 我们写在xml中的sql语句,是如何和mapper接口中的方法相对应。换句话说,我们再调用接口方法,接口方法是如何执行到相对应的方法。
要解开这两个问题,开代码最为直接。接下来就开始了,我苦逼的看源码的过程了。接下来依次解决这个两个问题的疑惑。
问题一: 接口是不能被实例化,那么在service中定义的mapper如何被实例化的
我们都知道接口是不能被实例化的,那么我们通常是需要些一个mapper的接口,同过spring的注解方法就直接可以在service类中使用,这个很奇怪。一定是spring容器在启动的时候对相应的mapper的接口做了手脚。那有了这个猜想我们就要同过源码去验证我的猜想。为了看spring在启动过程中对mybatis中mapper做了什么手脚,首先我们要创建一个spring boot+mybatis的项目,在相应的mysql中一个简单的表,在项目中创建对的bean,mapper,xml等相应的东西。启动项目,使用debug一步一步的跟踪spring启动的时候的代码突然有个东西映入我眼帘,“ClassPathMapperScanner“,好家伙,这是什么东东。仔细一看原来扫描mapper的工具类。打开ClassPathMapperScanner类,我看到入下方法:
其中第一个用红线框标出来的代码是用来扫描相应的mapper。那第二个红线框标出来的processBeanDefinitions(beanDefinitions);根据字面意思是处理扫描出来的mapper的吧。我们继续跟进代码,看下图:
看到图中用红线框标出的代码,很明显spring在启动的时候将相应的beanClass替换为this.mapperFactoryBeanClass。那么this.mapperFactoryBeanClass又是何方神圣,继续看代码,如图:
那我们继续看MapperFactoryBean到底有什么能耐,继续看源码:
原来MapperFactoryBean是一个FactoryBean用于生产Mapper想对应的实例。
原本事情到这里应该告一段落,可是为了能更好的和下一个问题有一个衔接。我们还要继续往下看。我们在MapperFactoryBean中找到getObject()方法:
我们通过getSqlSession().getMapper(this.mapperInterface);方法一直跟踪到MapperRegistry-->getMapper()方法:
我们在getMapper方法中看到mapperProxyFactory.newInstance(sqlSession);从字面意思中我们可以发现,这个方法应该使用动态代理生成mapper的对象。为了一探究竟,继续查看MapperProxyFactory源码。
的确,MapperProxyFactory是通过动态代理生成mapper对象。请注意我用红线框标记代码,MapperProxy是个InvocationHandler的实现类(大家都知道,动态代理少不了),这个行代码和我下一个问题仅仅相关的。欲知原由,且看下一问题的分解。
问题二:写在xml中的sql语句,是如何和mapper接口中的方法相对应
在spring-boot项目启动过程时,spring-boot会通过MybatisAutoConfiguration来初始化SqlSessionFactory。我们知道SqlSessionFactory是获取访问数据的session。那在创建SqlSessionFactory时Mybatis又做了什么?看下图:
从代码中我们看到,SqlSessionFactory 是由MybatisSqlSessionFactoryBean创建而来。那么在SqlSessionFactoryBean中是否有和我们提出的问题相关的内容吗。现在让我们打开SqlSessionFactoryBean的真面目。现在我们一起看下庐山真面目:
请注意我用红线框标出的代码,难道 MybatisXmlConfigBuilder 就是用来解析xml中的sql语句的吗? 我们把buildSqlSessionFactory方法拉到最后,我们看到如下代码:
最为重要的是xmlMapperBuilder.parse(),难道解析的细节就在这里面吗?我那激动的小心脏呀。。。。
继续跟进xmlMapperBuilder.parse()方法。
用红线框中的第三个方法,看字面意思就是处理sql语句的。继续跟进这个方法:
我们看到parseStatementNode()方法,这里面就是解析xml文件中的sql语句。一起看下图:
这里面就是真正处理xml中sql语句。
然而这还没有解答我们提出的问题,xml和mapper接口中的方法如何对应上的。那我们接着往下看代码。在
parseStatementNode()方法的最后我们看到入下代码:
我们打开addMappedStatement方法
通过这个方法,把xml的语句转换为MappedStatement对应,然后存入configuration中的map对象中,其中map的key,就是xml中的id属性。如下图所示:
然后我们再回到第一个问题的最后,我提的MapperProxy,他是InvocationHandler实现类。我们现在可以一睹他的真面目。
在这我就只截取他的invoke方法(知道动态代理的都知道)。我们继续跟进MapperMethod方法中execute方法。
我们注意用红线框的方法,打开该方法。
该方法的第一行,我们很清楚的看到了 MappedStatement。第二问题的最终答案也在于此!
(题外话:以前在网上看别人写的技术博客,总是感觉这么简单的东西,还要写。现在自己写东西了,平时感觉简单的东西,突然不会了写不出来了。现在看来不管好于坏能写出来都很不错!)