在之前一系列文章中,我們介紹了MyBatis關(guān)于反射相關(guān)的工具類(lèi),今天開(kāi)始回歸主線(xiàn)任務(wù),繼續(xù)分析主流程源碼。我們欠下的技術(shù)債先放一下:
- Configuration
- TypeAliasRegistry
- TypeHandlerRegistry
大家還記得否舶替,在《MyBatis印象閱讀之起航篇》我們到了分析XMl資源文件的步驟。那么接下來(lái)我們開(kāi)始分析源碼杠园。
1.XMLConfigBuilder源碼分析
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
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
在分析這塊源碼顾瞪,我們可以參照官網(wǎng)的configuration下有哪些標(biāo)簽和作用,MyBatis官網(wǎng) XML配置
1.1 propertiesElement方法解析
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
代碼不難理解抛蚁,我們從源碼中可以看到配置來(lái)源有三種陈醒,分別resource、url最后是直接配置瞧甩。如果屬性不止在一個(gè)地方進(jìn)行了配置钉跷,那么MyBatis將按照下面的順序加載:
在 properties 元素體內(nèi)指定的屬性首先被讀取。
然后根據(jù) properties 元素中的 resource 屬性讀取類(lèi)路徑下屬性文件或根據(jù) url 屬性指定的路徑讀取屬性文件肚逸,并覆蓋已讀取的同名屬性爷辙。
最后讀取作為方法參數(shù)傳遞的屬性,并覆蓋已讀取的同名屬性朦促。
因此膝晾,通過(guò)方法參數(shù)傳遞的屬性具有最高優(yōu)先級(jí),resource/url 屬性中指定的配置文件次之务冕,最低優(yōu)先級(jí)的是 properties 屬性中指定的屬性血当。
這里最需要關(guān)注的就是最后兩句代碼,我們會(huì)把解析好的值放入解析器和全局變量,供之后解析使用臊旭。
1.2 settingsAsProperties方法解析
在分析源碼之前落恼,我們先通過(guò)官網(wǎng)來(lái)了解有哪些屬性:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
接下來(lái)我們?cè)賮?lái)看源碼:
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
// localReflectorFactory = new DefaultReflectorFactory();
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
這個(gè)方法源碼邏輯也比較簡(jiǎn)單,去除配置的setting屬性离熏,通過(guò)MetaClass校驗(yàn)Configuration是否有對(duì)應(yīng)屬性的set方法领跛,如果沒(méi)有就直接報(bào)錯(cuò)。這一步主要還是用來(lái)保護(hù)屬性的正確性撤奸。
接下來(lái)是
loadCustomVfs(settings);
loadCustomLogImpl(settings);
這個(gè)我沒(méi)用過(guò)這些配置,不做分析喊括,看名字應(yīng)該是自定義Vfs和Log胧瓜。我們接下來(lái)繼續(xù)看typeAliasesElement方法。
1.3 typeAliasesElement源碼解析
我們還是先來(lái)看下官方介紹:
類(lèi)型別名是為 Java 類(lèi)型設(shè)置一個(gè)短的名字郑什。 它只和 XML 配置有關(guān)府喳,存在的意義僅在于用來(lái)減少類(lèi)完全限定名的冗余。
例如:
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
繼續(xù)來(lái)看源碼:
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
這個(gè)方法說(shuō)難也不難蘑拯,主要還是在于typeAliasRegistry我們還未做過(guò)分析钝满,但是如果只是感念理解的話(huà)也很容易,我們?cè)谶@里可以當(dāng)做是一個(gè)Map來(lái)存儲(chǔ)原屬性的別名申窘。
之后按照順序是pluginElement方法弯蚜,這是用來(lái)解析插件的。
1.4 pluginElement源碼解析
我們繼續(xù)來(lái)看官網(wǎng)怎么說(shuō):
MyBatis 允許你在已映射語(yǔ)句執(zhí)行過(guò)程中的某一點(diǎn)進(jìn)行攔截調(diào)用剃法。默認(rèn)情況下碎捺,MyBatis 允許使用插件來(lái)攔截的方法調(diào)用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
我們可以明白,這個(gè)插件是用來(lái)做擴(kuò)展用的贷洲,主要是攔截調(diào)用收厨,而幾個(gè)攔截點(diǎn)也給我們列舉了出來(lái),我們目前還不是很明白這些是干嘛的优构,但是先有個(gè)印象诵叁,知道plugins是可以擴(kuò)展的,而且擴(kuò)展點(diǎn)只有4個(gè)钦椭。我們先來(lái)看解析源碼:
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
例如配置為:
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
這款代碼要理解還是很簡(jiǎn)單的拧额,就是把配置轉(zhuǎn)化為Interceptor類(lèi),并添加到configuration中玉凯。我們繼續(xù)看objectFactoryElement代碼势腮。
1.5 objectFactoryElement源碼解析
我們繼續(xù)看官網(wǎng)說(shuō)明:
MyBatis 每次創(chuàng)建結(jié)果對(duì)象的新實(shí)例時(shí),它都會(huì)使用一個(gè)對(duì)象工廠(chǎng)(ObjectFactory)實(shí)例來(lái)完成漫仆。 默認(rèn)的對(duì)象工廠(chǎng)需要做的僅僅是實(shí)例化目標(biāo)類(lèi)捎拯,要么通過(guò)默認(rèn)構(gòu)造方法,要么在參數(shù)映射存在的時(shí)候通過(guò)參數(shù)構(gòu)造方法來(lái)實(shí)例化。 如果想覆蓋對(duì)象工廠(chǎng)的默認(rèn)行為署照,則可以通過(guò)創(chuàng)建自己的對(duì)象工廠(chǎng)來(lái)實(shí)現(xiàn)祸泪。
這個(gè)我們基本也不會(huì)去改動(dòng),所以我們簡(jiǎn)單看下源碼就行:
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties properties = context.getChildrenAsProperties();
ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
factory.setProperties(properties);
configuration.setObjectFactory(factory);
}
}
2.今日總結(jié)
我們分析了關(guān)于XML資源的部分解析資源源碼建芙,下一章我們會(huì)繼續(xù)接下來(lái)的分析没隘。