概述
插件是用来改变或者扩展 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