一、插件
Mybatis中允许我们在执行过程中,在一些节点处进行拦截,具体可以在如下对象的方法中进行拦截:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
先来个demo,要使用插件功能只需要实现Interceptor 接口,指定需要拦截的方法签名即可,并在配置文件中配置好自定义的插件。
<plugins>
<plugin interceptor="xxx.xxx.XXXPlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
假设我们需要统计Executor 的query方法的调用次数,首先我们需要实现Interceptor 接口,然后定义好query方法的方法签名:
@Intercepts({@Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})})
public class QueryCountPlugin implements Interceptor {
private int count;
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("query times:"+count++);
return invocation.proceed();
}
}
然后在全局配置文件中配置好插件:
<plugins>
<plugin interceptor="org.apache.ibatis.QueryCountPlugin"></plugin>
</plugins>
此时,只要有Executor 的query方法调用,就会不断的输出调用次数,所以插件的实现就是这么的简单,完全可以根据自身的需求去做定制化功能。
二、源码实现
第一部分简单了解了插件的使用,这部分我们将从源码中去探讨他的实现过程,mybatis中支持拦截的对象有Executor 、StatementHandler 、ResultSetHandler 、ParameterHandler 这四种对象。整体的实现逻辑是一致的,再次我们以Executor 的拦截作为分析对象。
之前分析可知,Executor 的实例化在SqlSessionFactory的openSession中,实例化完成后有这么一段调用。
executor = (Executor) interceptorChain.pluginAll(executor)
interceptorChain是Configuration对象中的一个InterceptorChain属性,里边有个保存Interceptor的集合interceptors。
public Object pluginAll(Object target) {
//从插件集合中不断取出对应插件,包装目标对象
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
在分析Configuration解析时,我们知道配置好的插件会保存在interceptors集合中,所以此时我们需要查看Interceptor 接口的默认实现方法plugin。
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
Plugin类实现了InvocationHandler接口,所以插件的实现,也是通过动态代理。
public static Object wrap(Object target, Interceptor interceptor) {
//获取拦截对象和对应的拦截方法的集合
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
//获取拦截对象的所有接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
//返回代理对象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
以上是动态代理的一个实现过程,具体的逻辑在于Plugin的invoke方法。
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)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
所以插件的整体实现逻辑比较简单,剩下的对象中也只需执行interceptorChain.pluginAll()方法即可。
三、总结
mybatis的插件功能在提供方便的同时,也存在一定的风险,因为插件的使用都是对底层调用的封装,如果调用不当就会导致底层业务逻辑错误。
以上,有任何不对的地方,请指正,敬请谅解。