深入Mybatis原始碼——配置解析
阿新 • • 發佈:2020-07-07
@[toc]
# 前言
上一篇分析了Mybatis的基礎元件,Mybatis的執行呼叫就是建立在這些基礎元件之上的,那它的執行原理又是怎樣的呢?在往下之前不妨先思考下如果是你會怎麼實現。
# 正文
熟悉Mybatis的都知道,在使用Mybatis時需要配置一個**mybatis-config.xml**檔案,另外還需要定義Mapper介面和Mapper.xml檔案,在config檔案中引入或掃描對應的包才能被載入解析(現在由於大多是SpringBoot工程,基本上都不會配置config檔案,而是通過註解進行掃描就行了,但本質上的實現和xml配置沒有太大區別,所以本篇仍以xml配置方式進行分析。),所以Mybatis的第一個階段必然是要去**載入並解析配置檔案**,這個階段在專案啟動時就應該完成,後面直接呼叫即可。載入完成之後,自然就是等待呼叫,但是我們在專案中只會定義Mapper介面和Mapper.xml檔案,那具體的實現類在哪呢?Mybatis是通過**動態代理**實現的,所以第二個階段應該是**生成Mapper介面的代理實現類**。通過呼叫代理類,最終會生成對應的sql訪問資料庫並獲取結果,所以最後一個階段就是**SQL解析**(引數對映、SQL對映、結果對映)。本文主要分析配置解析階段。
## 配置解析
Mybatis可以通過下面的方式解析配置檔案:
```java
final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
final Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
```
所以入口就是**build**方法(從名字可以看出使用了**建造者模式**,它和**工廠模式**一樣,也是解用於建立物件的一種模式,不過與**工廠模式**不一樣的是,前者需要我們自己參與構建的細節,而後者則不需要):
```java
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//讀取配置檔案
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());//解析配置檔案得到configuration物件,並返回SqlSessionFactory
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
```
這裡先是建立了一個**XMLConfigBuilder**物件,這個物件就是用來載入解析config檔案的,先看看它的構造方法中做了些什麼事情:
```java
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
```
需要注意的是這裡建立了一個**Configuration**物件,他就是Mybatis的核心CPU,儲存了所有的配置資訊,在後面的執行階段所需要的資訊都是從這個類取的,因為這個類比較大,這裡就不貼詳細程式碼了,讀者請務必閱讀原始碼熟悉該類。因為這個類物件儲存了所有的配置資訊,那麼必然這個類是全域性單例的,事實上這個物件的建立也只有這裡一個入口,保證了全域性唯一。
在該類的構造方法中,首先就註冊了核心元件的別名和對應的類對映關係:
```java
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
......省略
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
```
而註冊類在例項化時同樣也註冊了一些基礎型別的別名對映:
```java
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
......省略
registerAlias("ResultSet", ResultSet.class);
}
```
看到這相信你就知道parameterType和resultType屬性的簡寫是怎麼實現的了。回到主流程,進入到**parser.parse**方法中:
```java
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first