MyBatis 學(xué)習(xí)筆記(四)---源碼分析篇---配置文件的解析過(guò)程(二)
概述
接上一篇MyBatis 學(xué)習(xí)筆記(四)—源碼分析篇—配置文件的解析過(guò)程(一) 。上一篇我們介紹了properties 和settings配置的解析過(guò)程,今天我們接著來(lái)看看其他常用屬性的解析過(guò)程,重點(diǎn)介紹typeAliases,environments等配置的解析。
typeAliases的解析過(guò)程
一個(gè)簡(jiǎn)單的別名配置如下:
<typeAliases>
<typeAlias type="com.jay.chapter2.entity.ClassRoom" alias="ClassRoom"/>
<typeAlias type="com.jay.chapter2.entity.Student" alias="Student"/>
</typeAliases>
//or
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
如上,typeAliases配置的使用也比較簡(jiǎn)單,該配置主要是減少在映射文件中填寫(xiě)全限定名的冗余。下面我們來(lái)看看解析過(guò)程
//* XMLConfigBuilder
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//如果是package
String typeAliasPackage = child.getStringAttribute("name");
//(一)調(diào)用TypeAliasRegistry.registerAliases,去包下找所有類(lèi),然后注冊(cè)別名(有@Alias注解則用,沒(méi)有則取類(lèi)的simpleName)
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
//如果是typeAlias
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
//根據(jù)Class名字來(lái)注冊(cè)類(lèi)型別名
//(二)調(diào)用TypeAliasRegistry.registerAlias
if (alias == null) {
//alias可以省略
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
如上,該入口程序方法執(zhí)行流程如下:
- 根據(jù)節(jié)點(diǎn)名稱(chēng)判斷是否是package,如果是的話則調(diào)用TypeAliasRegistry.registerAliases,去包下找所有類(lèi),然后注冊(cè)別名(有@Alias注解則用,沒(méi)有則取類(lèi)的simpleName)
- 如果不是的話,則進(jìn)入另外一個(gè)分支,則根據(jù)Class名字來(lái)注冊(cè)類(lèi)型別名。
接下來(lái)我們按照兩個(gè)分支進(jìn)行分析。
package解析分支
按照前面說(shuō)的如果配置的是package的話,那么首先去包下找所有的類(lèi),然后注冊(cè)別名。
那么它是如何找到包下的所有類(lèi)的呢?帶著疑問(wèn)我們來(lái)看看源碼。
//* TypeAliasRegistry public void registerAliases(String packageName){ registerAliases(packageName, Object.class); } public void registerAliases(String packageName, Class<?> superType){ ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); //掃描并注冊(cè)包下所有繼承于superType的類(lèi)型別名 resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); for(Class<?> type : typeSet){ // if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { registerAlias(type); } } } //注冊(cè)類(lèi)型別名 public void registerAlias(Class<?> type) { //如果沒(méi)有類(lèi)型別名,用Class.getSimpleName來(lái)注冊(cè) String alias = type.getSimpleName(); //或者通過(guò)Alias注解來(lái)注冊(cè)(Class.getAnnotation) Alias aliasAnnotation = type.getAnnotation(Alias.class); if (aliasAnnotation != null) { alias = aliasAnnotation.value(); } registerAlias(alias, type); }
如上,流程主要有兩部分
- 通過(guò)ResolverUtil的find方法找到該包下所有的類(lèi),傳入的父類(lèi)是Object
- 循環(huán)注冊(cè)別名,只有非匿名類(lèi)及非接口及內(nèi)部類(lèi)及非成員類(lèi)才能注冊(cè)。
- 注冊(cè)別名最終還是調(diào)用registerAlias(alias, type)完成的。
接著我們?cè)賮?lái)看看ResolverUtil到底是如何查找包下的所有類(lèi)的。
//主要的方法,找一個(gè)package下滿(mǎn)足條件的所有類(lèi),被TypeHanderRegistry,MapperRegistry,TypeAliasRegistry調(diào)用
public ResolverUtil<T> find(Test test, String packageName) {
String path = getPackagePath(packageName);
try {
//通過(guò)VFS來(lái)深入jar包里面去找一個(gè)class
List<String> children = VFS.getInstance().list(path);
for (String child : children) {
if (child.endsWith(".class")) {
//將.class的class對(duì)象放入Set集合中,供后面調(diào)用
addIfMatching(test, child);
}
}
} catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
如上,核心是通過(guò)VFS來(lái)找到packageName下面的子包,包下面的class以及子包下面的class。PS: VFS 是虛擬文件系統(tǒng),用來(lái)讀取服務(wù)器里的資源。在此處我們不做分析。
根據(jù)Class名字注冊(cè)解析分支
//注冊(cè)類(lèi)型別名 public void registerAlias(String alias, Class<?> value) { if (alias == null) { throw new TypeException("The parameter alias cannot be null"); } // issue #748 String key = alias.toLowerCase(Locale.ENGLISH); //如果已經(jīng)存在key了,且value和之前不一致,報(bào)錯(cuò) //這里邏輯略顯復(fù)雜,感覺(jué)沒(méi)必要,一個(gè)key對(duì)一個(gè)value唄,存在key直接報(bào)錯(cuò)不就得了(與系統(tǒng)內(nèi)置的類(lèi)型別名相同的別名直接報(bào)錯(cuò)) if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) { throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'."); } TYPE_ALIASES.put(key, value); } public <T> Class<T> resolveAlias(String string) { try { if (string == null) { return null; } //這里轉(zhuǎn)個(gè)小寫(xiě)也有bug?見(jiàn)748號(hào)bug(在google code上) https://code.google.com/p/mybatis/issues //比如如果本地語(yǔ)言是Turkish,那i轉(zhuǎn)成大寫(xiě)就不是I了,而是另外一個(gè)字符(?)。這樣土耳其的機(jī)器就用不了mybatis了!這是一個(gè)很大的bug,但是基本上每個(gè)人都會(huì)犯...... String key = string.toLowerCase(Locale.ENGLISH); Class<T> value; //原理就很簡(jiǎn)單了,從HashMap里找對(duì)應(yīng)的鍵值,找到則返回類(lèi)型別名對(duì)應(yīng)的Class if (TYPE_ALIASES.containsKey(key)) { value = (Class<T>) TYPE_ALIASES.get(key); } else { //找不到,再試著將String直接轉(zhuǎn)成Class(這樣怪不得我們也可以直接用java.lang.Integer的方式定義,也可以就int這么定義) value = (Class<T>) Resources.classForName(string); } return value; } catch (ClassNotFoundException e) { throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e); } }
至此,我們的類(lèi)型別名就注冊(cè)和解析就全部完成了。
environments的解析過(guò)程
我們都知道MyBatis中environments配置主要是用來(lái)配置數(shù)據(jù)源信息,是MyBatis中一定會(huì)有的配置。首先我們還是來(lái)看看environments配置的使用。
<!-- 設(shè)置一個(gè)默認(rèn)的連接環(huán)境信息 -->
<environments default="development">
<!--連接環(huán)境信息,取一個(gè)任意唯一的名字 -->
<environment id="development">
<!-- mybatis使用jdbc事務(wù)管理方式 -->
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<!-- mybatis使用連接池方式來(lái)獲取連接 -->
<dataSource type="POOLED">
<!-- 配置與數(shù)據(jù)庫(kù)交互的4個(gè)必要屬性 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
如上,配置了連接環(huán)境信息,我們心中肯定會(huì)有個(gè)疑問(wèn),${} 這種參數(shù)是如何解析的?我一會(huì)再分析。
下面我們就來(lái)看看這個(gè)配置的解析過(guò)程。
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
//循環(huán)比較id是否就是指定的environment
if (isSpecifiedEnvironment(id)) {
//1 創(chuàng)建事務(wù)工廠TransactionFactory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//2創(chuàng)建數(shù)據(jù)源
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
//3.構(gòu)建Environment對(duì)象
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 將創(chuàng)建的Environment對(duì)象設(shè)置到configuration中
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
如上,解析environments 的流程有三個(gè):
- 創(chuàng)建事務(wù)工廠TransactionFactory
- 創(chuàng)建數(shù)據(jù)源
- 創(chuàng)建Environment對(duì)象
我們看看第一步和第二步的代碼
//* XMLConfigBuilder private TransactionFactory transactionManagerElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); //根據(jù)type="JDBC"解析返回適當(dāng)?shù)腡ransactionFactory TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a TransactionFactory."); } protected Class<?> resolveClass(String alias) { if (alias == null) { return null; } try { return resolveAlias(alias); } catch (Exception e) { throw new BuilderException("Error resolving class. Cause: " + e, e); } } //*Configuration typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
JDBC 通過(guò)別名解析器解析之后會(huì)JdbcTransactionFactory工廠實(shí)例。數(shù)據(jù)源的解析與此類(lèi)似最終得到的是PooledDataSourceFactory工廠實(shí)例。
下面我們來(lái)看看之前說(shuō)過(guò)的類(lèi)似${driver}的解析。其實(shí)是通過(guò)PropertyParser的parse來(lái)處理的。下面我們來(lái)看個(gè)時(shí)序圖。
這里最核心的就是第五步,我們來(lái)看看源碼
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
//就是一個(gè)map,用相應(yīng)的value替換key
private static class VariableTokenHandler implements TokenHandler {
private Properties variables;
public VariableTokenHandler(Properties variables) {
this.variables = variables;
}
@Override
public String handleToken(String content) {
if (variables != null && variables.containsKey(content)) {
return variables.getProperty(content);
}
return "${" + content + "}";
}
}
如上,在VariableTokenHandler 會(huì)將${driver} 作為key,其需要被替換的值作為value。傳入GenericTokenParser中。然后通過(guò)GenericTokenParser 類(lèi)的parse進(jìn)行替換。
至此,我們environments配置就解析完了。
總結(jié)
本文主要介紹typeAliases和environments配置的解析。然后還說(shuō)下${driver} 這種屬性的處理。希望對(duì)讀者朋友們有所幫助。
源代碼
https://github.com/XWxiaowei/mybatis
作者:碼農(nóng)飛哥
微信公眾號(hào):碼農(nóng)飛哥