解析plugins元素,完成mybatis插件的配置
Mybtis的插件機制是一個很強大的功能,它允許我們在Mybatis運行期間切入到Mybatis內部執行我們想要做的一些事情。
mybatis比較火的分頁插件page-helper
其實就是基於Mybatis的插件功能實現的。
Mybatis的plugins
DTD定義如下:
<!--ELEMENT plugins (plugin+)-->
<!--ELEMENT plugin (property*)-->a
<!--ATTLIST plugin
interceptor CDATA #REQUIRED
-->
plugins
標籤下必須定義一個或多個plugin
子節點。
plugin
子節點有一個必填的屬性interceptor
,該屬性指向一個實現了Interceptor
的類,同時在plugin
標籤下面允許出現零個或多個property
標籤,用來配置該插件運行時依賴的屬性。
比如:
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100" />
</plugin>
</plugins>
Interceptor
是mybatis提供的插件接口定義:
/**
* Mybatis 攔截器(插件)接口定義
*
* @author Clinton Begin
*/
public interface Interceptor {
/**
* 提供被攔截方法的代理實現,完成額外的業務處理
*
* @param invocation 代理
*/
Object intercept(Invocation invocation) throws Throwable;
/**
* 該方法用於處理被攔截對象,返回該對象本身或者爲其生成一個代理對象。
* <p>
* 如果返回的是代理對象,我們可以爲指定方法實現代理實現:{@link #intercept(Invocation)}
*
* @param target 被攔截的對象
*/
Object plugin(Object target);
/**
* 配置該插件運行時依賴的屬性
*/
void setProperties(Properties properties);
}
它定義了三個方法:
intercept
方法用於提供被攔截方法的代理實現,完成額外的業務處理。plugin
方法負責處理被攔截的對象,返回該對象本身或者爲其生成一個代理對象。setProperties
方法用於配置該插件運行時依賴的屬性。
其中intercept
方法的入參是一個Invocation
類型的對象,Invocation
對象是mybatis提供的一個方法反射操作封裝對象,他維護了特定的方法對象及其運行時依賴的參數。
Invocation
定義了三個屬性:
/**
* 被代理的對象
*/
private final Object target;
/**
* 被代理的方法
*/
private final Method method;
/**
* 被代理方法的入參
*/
private final Object[] args;
這三個屬性的賦值操作是在Invocation
的構造方法中完成的:
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
同時Invocation
不僅對外提供了這三個屬性的getter
方法,還提供了一個用於完成方法反射操作的proceed
方法:
/**
* 通過反射完成方法調用
*
* @return 方法返回值
*/
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
補充完基礎知識之後,我們回到plugins
元素的解析操作上來:
// 插件配置
pluginElement(root.evalNode("plugins"));
在pluginElement
方法中,XmlConfigBuilder
會依次處理所有的plugin
子節點,獲取其interceptor
的屬性值,交給BaseBuilder
的resolveClass(String)
方法獲取具體的插件實現類,
並讀取plugin
子節點下的所有property
屬性配置生成Properties
對象。
/**
* 解析plugins節點
*
* @param parent plugins節點
*/
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 處理每一個interceptor
String interceptor = child.getStringAttribute("interceptor");
// 獲取運行時屬性配置
Properties properties = child.getChildrenAsProperties();
// 獲取插件的實現
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 初始化插件配置
interceptorInstance.setProperties(properties);
// 註冊插件,在插件職責鏈中註冊一個新的插件實例
configuration.addInterceptor(interceptorInstance);
}
}
}
> 因爲這裏用的是resolveClass
方法來獲取插件的實現類,所以對於插件的配置,也是支持別名機制的。
之後通過反射得到具體的插件對象實例,並調用其setProperties
方法配置其運行時需要使用的屬性。
完成上訴操作之後,調用configuration
的addInterceptor(Interceptor)
方法完成保存插件的工作。
/**
* 添加一個攔截器
* @param interceptor 攔截器
*/
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
在configuration
的addInterceptor(Interceptor)
方法中將添加插件的工作交給了interceptorChain
的addInterceptor
方法來完成。
interceptorChain
屬性在Configuration
中被硬編碼爲InterceptorChain
類型的實例:
/**
* 插件攔截鏈(Mybatis插件)
*/
protected final InterceptorChain interceptorChain = new InterceptorChain();
InterceptorChain
定義了一個List<interceptor>
類型的屬性interceptors
用於存放mybatis中所有的插件實現類。
/**
* 所有插件
*/
private final List<interceptor> interceptors = new ArrayList<>();
對外暴露的addInterceptor
方法就是用來往interceptors
集合中添加Interceptor
實現的:
/**
* 添加一個新的{@link Interceptor#}實例
*/
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
除了addInterceptor
方法之外,InterceptorChain
還對外暴露了getInterceptors
和pluginAll
兩個方法。
其中getInterceptors
用於獲取當前所有Interceptor
實例:
/**
* 獲取所有的Interceptor
*/
public List<interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
pluginAll
方法則負責依次調用所有的Interceptor
實例的plugin
方法對目標對象進行處理。
/**
* 調用所有插件的{@link Interceptor#plugin(Object)}方法,包裝指定對象
*/
public Object pluginAll(Object target) {
// 多層攔截器對目標對象進行代理,會形成一條職責鏈
for (Interceptor interceptor : interceptors) {
// 處理被攔截對象,返回該對象本身或者爲其生成一個代理對象。
target = interceptor.plugin(target);
}
return target;
}
如果多個Interceptor
對目標對象進行了代理,那麼多層代理之間就會形成一條職責鏈。
職責鏈模式(責任鏈模式)也是一種常見的行爲型設計模式:
- 責任鏈模式是由多個對象構成的一條鏈式結構,該結構中的對象之間通過前者持有後者引用的方式貫穿鏈路.
- 用戶在發起對責任鏈的請求時,並不知道請求會被責任鏈中的哪一個對象處理,這樣做用戶只需要直接對責任鏈請求即可,而不需要針對每一個具體的對象發起請求.
責任鏈模式按照處理請求方式的不同可以分爲純責任鏈和不純的責任鏈:
- 純責任鏈:
> 在處理請求過程中,每個節點對於請求的處理方式有且僅有處理和不處理兩種,同時請求一定會被責任鏈中的某一個節點處理,當這個節點處理了請求之後,他一定不會將請求繼續往下順延,即,終止請求在鏈路中繼續運行.
- 不純的責任鏈
> 在不純的責任鏈模式內,一個請求可能會被多個節點處理,即每個節點都可能處理請求的一部分,其後續節點同樣還有可能處理 請求的部分,甚至,還可能存在沒有任何節點處理請求的情形.
理論上講,由interceptors
形成的代理對象責任鏈應該是不純的責任鏈。