![3c292d9e1abb56e70eef30da46816870.png](https://i-blog.csdnimg.cn/blog_migrate/ed7dd4361ece22c7830fbaf84509a515.jpeg)
在本系列的第二篇文章中小编就说了,Mybatis的配置信息都是由Configuration来保存的,本篇文章我们就重点来看Mybatis的解析过程。在学习完本篇,你会完全掌握对Mybatis配置的认识,是你产生新的认识。由于头条的排版问题,代码展示不美观。建议到小编个人博客。私信:深入浅出Mybatis系列 获取地址
![01deb883344236e8a978c11d190f7466.png](https://i-blog.csdnimg.cn/blog_migrate/b1ef9db25dfa5c39cae968919d56fdac.jpeg)
![4b83a073ed2fa645e7e74cbdc42cb31c.png](https://i-blog.csdnimg.cn/blog_migrate/1ec385b3d5b7b63820f8745c36f93b65.jpeg)
本篇的源码就从下面的代码片段中开始。
![677b60b47d9e99fa65018f34c4a9b95a.png](https://i-blog.csdnimg.cn/blog_migrate/5167152f6c8d1fcffcd6860a8a9a6f86.jpeg)
- 首先拿到配置文件输入流.
- 通过 SqlSessionFactoryBuilder().build(mapperInputStream);创建 SqlSessionFactory。而我们看SqlSessionFactory的接口定义中就知道是包含了获取Configuration方法。因此断定Configuration的解析入口一定在 SqlSessionFactoryBuilder.build()方法中。
文档解析器SAX解析,这点不做重点研究,一笔带过。
SqlSessionFactoryBuilder
![0eb5d24173e4a25eda123463a57bc40c.png](https://i-blog.csdnimg.cn/blog_migrate/865524d85acca8f3f8f30636c8c05a25.jpeg)
XMLConfigBuildery初始化时候已经生成了Document,Mybatis配置文件的具体实现看 XMLConfigBuilder.parse()
XMLConfigBuilder
![c9b97abf5110ce16b6fd467d9201c970.png](https://i-blog.csdnimg.cn/blog_migrate/32e6e143f314a229dc435adfed02a185.jpeg)
parseConfiguration方法才是真正将XML解析成Java代码的地方。我们看Mybatis都如何去解析自己的配置节点
配置文件
我们以下面的配置文件为例子,看Mybatis如何解析,已经了解他们在Mybatis中的作用
![3aaa555bb75685e3df967fd6994e4a84.png](https://i-blog.csdnimg.cn/blog_migrate/d007b7d4d4d754914b7418218b4d3b1d.jpeg)
parseConfiguration分析
- propertiesElement(root.evalNode("properties"));
从配置文件中,读取 properties标签,生成Properties对象,交给XPathParser保管,在解析配置节点的属性时候 当遇到 ${}站位符,就从properties指定的文件中读取数值。
这点的处理逻辑在 PropertyParser实现,想要深入研究自行学习。
- Propertiessettings=settingsAsProperties(root.evalNode("settings"));
读取 settings节点,生成Properties对象,Mybatis会根据其,去用户化配置系统的实现类,或者是默认值。
这点的逻辑逻辑在 settingsElement(Propertiesprops)方法中,比如: 上面配置文件中自定义了日志的实现类。logImpl=>LOG4J
- typeAliasesElement(root.evalNode("typeAliases"));
细心的同学可能发现,settings中logImpl的实现,配置文件中写的是LOG4J。那Mybatis是如何根据LOG4J找到具体的Class对象的呢? 其实就是typeAliases标签来,配置的,但是我们的配置文件中没有写这个节点呀,Mybatis是如何做的呢? 这里我们就要引入一个类 TypeAliasRegistry。小编叫别名注册器,代码非常简单。
![a02d59eb6e7216eabce4d9558d2d608e.png](https://i-blog.csdnimg.cn/blog_migrate/fc774de95460d184ecd9c9951a0be715.jpeg)
其实就是一个Map集合,key是别名value是类型
![a8d02de1fafe1e9285f0f6fe30ff84aa.png](https://i-blog.csdnimg.cn/blog_migrate/ea6633346764d52edb85b1ec50632dc6.jpeg)
- pluginElement(root.evalNode("plugins"));
Mybatis插件都是拦截器的原理,读取拦截器实现类,添加到 Configuration的拦截链中 InterceptorChain,在执行时候执行拦截器的逻辑
![c4197628230240ae2f7f7850eebd9279.png](https://i-blog.csdnimg.cn/blog_migrate/ed5390b416d07398dc9bb0a6a0ea3a33.jpeg)
这里我们主要分析下Mybatis的拦截器接口,以及如何去扩展它
![d073d41de1a065418ce043e01256730e.png](https://i-blog.csdnimg.cn/blog_migrate/ad9dc0eb79f3f093099d793838de9048.jpeg)
自定的拦截器仅仅继承Interceptor是不够的,同时也要用@Intercepts和@Signature来修饰, Plugin.wrap(target,this)时候会解析这两个注解。 那么如何使用这两个注解呢
Type的类型只能是: StatementHandler | ParameterHandler | ResultSetHandler | Executor 类或者子类
![f36281cc5b2a2dee88ec2b56fcc6cb1c.png](https://i-blog.csdnimg.cn/blog_migrate/1f7e747eb34ceebf02e7100551f8473e.jpeg)
自定义一个拦截器,随便执行一个查询操作
![6c93df4f477068cb67a7013dfe6b58cd.png](https://i-blog.csdnimg.cn/blog_migrate/45cc27fa7a1e151753fbb757fe5349e8.jpeg)
![168a96d398f2c92c69ffe3bcba602fbe.png](https://i-blog.csdnimg.cn/blog_migrate/3521ef1e591286c28aca9a4152bf23b1.jpeg)
以上就是Mybatis的拦截器,推荐可以看看最经典的拦截器: 分页插件。它的逻辑就是,生成Page分页对象,在执行查询时候拼装sql的limit信息,有兴趣的可以深入研究。
- ObjectFactory、ObjectWrapperFactory、ReflectorFactory这三个对象都属于MetaObject的组件,从名字上看就是生成对象的,主要用做 数据库返回结果,通过这三个类,生成Java中对应的类。
- environmentsElement(root.evalNode("environments"));
比较容易解释了,读取environments节点default属性指定的环境信息。
- databaseIdProviderElement(root.evalNode("databaseIdProvider"));
Mybatis是支持多种数据库的,在使用Mybatis编写Mapper的时候,可以为同一个方法编写多种数据库的描述,Mybatis在启动时候会自动读取数据库类型,然后根据类型去查询Mapper配置中databaseId对应的sql信息。
主要Mysql数据库名字是MySQL,Oracle的名字是Oracle,配置文件中的value要与Mapper配置中的databaseId相对应。一般开发很少这样去写。虽然Mybatis提供这样的能力。
![46ff07e6793101fa10c1b993c744619e.png](https://i-blog.csdnimg.cn/blog_migrate/7ea4ce4cb8a7a401091454c08f48eb5a.jpeg)
- typeHandlerElement(root.evalNode("typeHandlers"));
这个也比较容易理解,类的设计很简单和TypeAliasRegistry很类似具体实现类是TypeHandlerRegistry。就是负责数据库类型和Java类型的转换。
- mapperElement(root.evalNode("mappers"));
终于读取到Mapper信息了,这个是Mybatis,对象关系映射核心的处理逻辑。 sql的配置信息BoundSql对象,这里面涉及到占位符和变量符的问题。这点在<>已经讲过了。 而Mapper类在Mybatis最终会转换成方法签名 MethodSignature 什么是方法签名,就是对一个方法的描述。比如: 返回值类型是Void,是集合还是实体类。以及获取@Param注解标记的入参的别名。 以及在执行数据库交互时候,获取接口的入参。这部分逻辑在 MapperMethod中, MethodSignature.convertArgsToSqlCommandParam()方法用来处理 @Param注解。如下图所示。
![c93887878ec2b7379e7af6a44484b211.png](https://i-blog.csdnimg.cn/blog_migrate/ea458e4b2bf78044ee5df50fb216ffec.jpeg)
![b06d07151191379c9e66bba8a9f91d01.png](https://i-blog.csdnimg.cn/blog_migrate/ab99d21e396c8ed81169676f7c8768a1.jpeg)
上面我们其实已经讲了,Mybatis是如何拿到Mapper类的参数了,以及Mapper类在Java中信息保存的载体。那么如何拿到BoundSql呢? 其实就在 MappedStatement中, MappedStatement即包含了 MethodSignature也包含了 BoundSql。而MappedStatement是在应用启动时候读取 mappers配置节点时候就生成的并保存在 Configuration。
![ba7ebea532c7eb950fcc47f2a4d1a7d1.png](https://i-blog.csdnimg.cn/blog_migrate/83983a4c0541166010dc8c3f8681eab7.jpeg)
MapperBuilderAssistant是具体生成MappedStatement的。而调用MapperBuilderAssistant有两种实现
![ef63696e85f29d7d673a0e99968b6119.png](https://i-blog.csdnimg.cn/blog_migrate/8d8a3c54b73057e9a927b62346be3729.jpeg)
- XMLMapperBuilder 读取xml配置来生成
- MapperAnnotationBuilder 读取注解来生成配合使用的注解 configuration.addMappers(mapperPackage)>MapperRegistry.addMapper()>MapperAnnotationBuilder.parse()。
到这里 mappers节点也已经读取完了,Mapper类方法签名信息保存在MethodSignature,Sql信息保存在BoundSql。在执行sql时候,会将参数与sql拼装起来使用,当拼装好的sql发送到数据库服务器,并受到服务器返回后,Mybatis会将数据库对象,转行成Java类数据对象。这部分逻辑我们下一篇主要来讲解。另外这里涉及到BoundSql的有两个知识点: 1.占位符# 2.变量符$ 在本系列第二篇已经说过了,这里不再赘述。
![399c39c7c79e3958bfd48266a380a796.png](https://i-blog.csdnimg.cn/blog_migrate/57b5917e2a343f223610fc63158c88d4.jpeg)