Mybatis插件配置解析
MyBatis 允許你在映射語句執行過程中的某一點進行攔截調用。默認情況下,MyBatis 允許使用插件來攔截的方法調用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
插件的配置通過<plugins>
標籤完成,Mybatis文檔中給出的例子如下:
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
在XMLConfigBuilder
的配置解析邏輯中,pluginElement(XNode parent)
方法用於解析插件配置。代碼如下:
private void parseConfiguration(XNode root) {
...
pluginElement(root.evalNode("plugins"));
...
}
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
// 遍歷plugins標籤的所有plugin子標籤
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
// 將所有的plugin標籤中聲明的所有property轉化爲Properties對象
Properties properties = child.getChildrenAsProperties();
// 實例化配置的plugin
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 將plugin需要的屬性初始化到plugin中
interceptorInstance.setProperties(properties);
// 將初始化完成的plugin放入到Configuration對象的interceptorChain屬性中
configuration.addInterceptor(interceptorInstance);
}
}
}
可以看到,插件配置被解析時就已經創建了插件的實例對象,而每個插件是按照攔截器模式進行處理業務的,我們這裏可以再次考察兩個問題:
- 插件的保存順序如何?
- 攔截器接口到底是怎樣的格式?
插件的保存順序
要考察插件的保存順序,我們必須深入到如下代碼中:
configuration.addInterceptor(interceptorInstance);
考察Configuration
對象的addInterceptor(Interceptor)
方法,可以發現:
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
該方法僅僅爲攔截器鏈添加了一個攔截器罷了,具體的攔截器添加邏輯在InterceptorChain
中,那麼攔截器鏈到底是怎樣的結構呢?其實攔截器鏈就是一個鏈表:
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
}
其添加邏輯更是簡單,完全不用考慮攔截器的執行順序,僅僅是將鏈表中添加一個節點罷了。
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
綜上所述,我們在Mybatis配置文件中配置的所有插件,在配置解析完畢後,會變成一個攔截器,並存儲於Configuration對象的攔截器鏈中,大致邏輯如下圖所示:
【mybatis-config.xml - <plugins>】
| ^
v |
【XMLConfigBuilder.parseConfiguration(parser.evalNode("/configuration")】 // 解析配置文件
| ^
v |
【XMLConfigBuilder.pluginElement(root.evalNode("plugins"))】 // 解析plugins標籤
| ^
v |
【XMLConfigBuilder.configuration.addInterceptor(interceptorInstance)】// 創建攔截器,並將攔截器放入到configuration對象的攔截器鏈中
| ^
v |
【interceptorChain.addInterceptor(interceptor);】
攔截器接口格式
通過上一部分我們知道,Mybatis的插件最後都會被作攔截器處理,這裏我們考察一下在Mybatis中攔截器接口的格式:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
該攔截器接口共聲明瞭三個方法:
- setProperties(Properties properties):通過上一節分析我們知道,該方法是在解析plugin標籤的最後一步,將plugin標籤的子標籤中聲明的所有properties設置到攔截器實例中。
- plugin(Object target):該方法用於聲明通過何種方式實現攔截器模式,Mybatis默認情況下是使用JDK動態代理的方式實現的攔截器模式,你可以覆蓋該方法對其進行更改,之後介紹到攔截器具體執行步驟時,會對比攔截器模式這樣實現的好處與缺點
- intercept(Invocation):該方法是攔截器執行的真正業務邏輯。
如此看來,Mybatis的攔截器接口聲明的三個方法分別用於:初始化、構建以及真正的業務操作。將三者解耦開來,是一種蠻不錯設計。
至此,我們已經分析完,Mybatis插件初始化過程,至於更多關於插件執行的細節,我們會在後面進行更深入的分析。不過現在先讓我們把Mybatis的配置解析分析完,接下來讓我們去看一下最重要的<mappers>
標籤的解析。