概述
插件是用來改變或者擴展 Mybatis 的原有的功能,Mybatis 的插件就是通過繼承 Interceptor 攔截器實現的。在沒有完全理解插件之前禁止使用插件對 Mybatis 進行擴展,又可能會導致嚴重的問題。
Mybatis 中能使用插件進行攔截的接口和方法如下:
- Executor(update、query 、 flushStatment 、 commit 、 rollback 、 getTransaction 、 close 、 isClose)
- StatementHandler(prepare 、 paramterize 、 batch 、 update 、 query)
- ParameterHandler(getParameterObject 、 setParameters )
- ResultSetHandler(handleResultSets 、 handleCursorResultSets 、 handleOutputParameters)
關鍵接口
Interceptor
package org.apache.ibatis.plugin;
import java.util.Properties;
public interface Interceptor {
//執行攔截邏輯的方法,調用代理對象的invoke方法
Object intercept(Invocation invocation) throws Throwable;
//target是被攔截的對象,它的作用就是給被攔截的對象生成一個代理對象
Object plugin(Object target);
//讀取在plugin中設置的參數
void setProperties(Properties properties);
}
Invocation
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object getTarget() {
return target;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return args;
}
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
示例:慢查詢檢測插件
配置類
@Intercepts({
@Signature(type=StatementHandler.class,method="query",args={Statement.class, ResultHandler.class})
// @Signature(type=StatementHandler.class,method="query",args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
})
public class ThresholdInterceptor implements Interceptor {
private long threshold;
@Override
public Object intercept(Invocation invocation) throws Throwable {
long begin = System.nanoTime();
//執行query方法
Object ret = invocation.proceed();
long runTime = (System.nanoTime()- begin)/1000;
if(runTime >= threshold){
Object[] args = invocation.getArgs();
//拿到的其實是PreparedStatement的一個動態代理
//InvocationHandler是PreparedStatementLogger
Statement stat = (Statement) args[0];
//反射工具類
MetaObject metaObjectStat = SystemMetaObject.forObject(stat);
//還記得h.invoke()嗎
PreparedStatementLogger statementLogger = (PreparedStatementLogger)metaObjectStat.getValue("h");
Statement statement = statementLogger.getPreparedStatement();
//TODO 日誌相關操作
System.out.println("sql語句:“"+statement.toString()+"”執行時間爲:"+runTime+"毫秒,已經超過閾值!");
}
return ret;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
this.threshold = Long.valueOf(properties.getProperty("threshold"));
}
}
關於
@Signature(type=StatementHandler.class,method="query",args={Statement.class, ResultHandler.class})
type:攔截的類型
method:方法
args:參數的類型
配置文件
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<plugin interceptor="com.enjoylearning.mybatis.Interceptors.ThresholdInterceptor">
<property name="threshold" value="10"/>
</plugin>
<!-- <plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="pageSizeZero" value="true" />
</plugin> -->
</plugins>
</configuration>
運行結果
隨便做個 select 測試,結果如下
如果真正用在項目裏,閾值應該設置大一些。
原理
責任鏈模式
責任鏈模式:就是把一件工作分別經過鏈上的各個節點,讓這些節點依次處理這個工作;和裝飾器模式不同,每個節點都知道後繼者是誰;適合爲完成同一個請求需要多個處理類的場景。
角色
- Handler:定義了一個處理請求的標準接口
- ConcreteHandler:具體的處理者,處理它負責的部分,根據業務可以結束處理流程,也可以將請求轉發給它的後繼者
優點
- 降低耦合度。它將請求的發送者和接收者解耦。
- 簡化了對象。使得對象不需要知道鏈的結構。
- 增強給對象指派職責的靈活性。通過改變鏈內的成員或者調動它們的次序,允許動態地新增或者刪除責任。
- 增加新的請求處理類很方便。
源碼
XMLConfigBuilder
類 XMLConfigBuilder 裏的 parseConfiguration 方法裏,有一步是
//解析<plugins>節點
pluginElement(root.evalNode("plugins"));
跟 pluginElement:
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對象,底層使用ArrayList保存所有的插件
//這裏體現了責任鏈模式的順序
configuration.addInterceptor(interceptorInstance);
}
}
}
Configuration
可以看到 4 種對象的實例化的方法都在這個類裏。
具體實例化的方法裏都有 interceptorChain.pluginAll 方法。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//通過裝飾器,添加二級緩存的能力
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//通過interceptorChain遍歷所有的插件爲executor增強,添加插件的功能
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
InterceptorChain
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
//注意這裏,調用plugin方法,這是自己重寫的方法
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
重寫的 plugin 方法,其實是參照 ExamplePlugin 的。
@Intercepts({})
public class ExamplePlugin implements Interceptor {
private Properties properties;
@Override
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
this.properties = properties;
}
public Properties getProperties() {
return properties;
}
}
Plugin
public class Plugin implements InvocationHandler {
//封裝的真正提供服務的對象
private final Object target;
//自定義的攔截器
private final Interceptor interceptor;
//解析@Intercepts註解得到的signature信息
private final Map<Class<?>, Set<Method>> signatureMap;
//靜態方法,用於幫助Interceptor生成動態代理
public static Object wrap(Object target, Interceptor interceptor) {
//解析Interceptor上@Intercepts註解得到的signature信息
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();//獲取目標對象的類型
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//獲取目標對象實現的接口(攔截器可以攔截4大對象實現的接口)
if (interfaces.length > 0) {
//使用jdk的方式創建動態代理
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//獲取當前接口可以被攔截的方法
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {//如果當前方法需要被攔截,則調用interceptor.intercept方法進行攔截處理
//自己重寫的intercept方法,就是在這裏被調用
return interceptor.intercept(new Invocation(target, method, args));
}
//如果當前方法不需要被攔截,則調用對象自身的方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
//省略了一些方法
}
相關資料
https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/Interceptor.md