mybatis的plugins(插件)是用来对mybatis运行中的方法进行拦截的,其主要拦截的方法有:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
plugins从创建到运行主要分为了三个部分:
- 解析Plugins配置生成Intercpetor责任链
- 创建Executor,ParameterHandler,ResultSetHandler ,StatementHandler实现类时,将实现类的实例与Intercpetor生成嵌套代理对象(此代理对象就像一颗洋葱,层层包裹,最里面才是被代理的对象)
- 代理对象被调用时Interceptor执行Intercepts进行拦截处理
解析Plugins配置生成Intercpetor责任链
-
Configuration在创建时会解析mybatis-config.xml中的
<plugins>
标签中的plugin,其中plugin中指向的类都是Interceptor的实现类,在解析是会调用Configuration的addInterceptor
将其实例化后全部加入Configuration中的InterceptorChain中.private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); ... Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor) .newInstance(); ... configuration.addInterceptor(interceptorInstance); } } }
-
Configuration中创建Executor,StatementHandler,parameterHandler,ResultSetHandler时调用对应的newXXX方法,方法中调用Configuration中的属性interceptorChains的
pluginAll
方法public Executor newExecutor(Transaction transaction, ExecutorType executorType) { ... executor = (Executor) interceptorChain.pluginAll(executor); ... }
-
InterceptorChain中的
pluginAll
方法运行时会去遍历Interceptor集合,调用集合中对象的plugin方法,而这些对象都是Interceptor的实现类,实现类中的plugin方法会去调用Plugin代理类的warp
方法,进行当前Interceptor实现类的代理对象生成。
org.apache.ibatis.plugin.InterceptorChain#pluginAll
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
注意target在循环中被赋值,说明target对象在进行代理嵌套
org.apache.ibatis.plugin.Interceptor(实现类)#plugin
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
其中plugin方法的参数target为第二步调用pluginAll
时传入的创建对象(Executor,StatementHandler,
parameterHandler,ResultSetHandler)或者为warp后的代理对象。
生成嵌套代理对象
首先,我们要明确一点,Plugin类是一个JDK代理类应为他实现了InvocationHandler
,只是这个代理类有点不一样,比我们认识的单纯的代理类多了一些东西。
-
org.apache.ibatis.plugin.Plugin#getSignatureMap
方法,此方法用于打包Interceptor实现类中的Signature
注解集合。
首先我们看一下
Signature
注解中有哪些属性public @interface Signature { Class<?> type();//要被拦截的对象 String method();//要被拦截对象中的具体方法名称 Class<?>[] args();//要被拦截对象中方法的具体参数对象集合 }
通过以上三点,来确定需要拦截的方法到底是哪一个
回过头来我们继续看
org.apache.ibatis.plugin.Plugin#getSignatureMap
方法,此方法获取Interceptor实现类的注解Intercepts interceptsAnnotation = interceptor.getClass() .getAnnotation(Intercepts.class); ... Signature[] sigs = interceptsAnnotation.value();
如上我们获得了一个Sigature注解的数组,遍历Signature数组通过Signature中的三个属性,我们将解析后的数据存放到一个以类为Key,方法集合为value的Map中
Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>(); for (Signature sig : sigs) { Set<Method> methods = signatureMap.get(sig.type()); if (methods == null) { methods = new HashSet<Method>(); signatureMap.put(sig.type(), methods); } try { Method method = sig.type().getMethod(sig.method(), sig.args()); methods.add(method); } catch (NoSuchMethodException e) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); } }
最终返回此Map,后面拦截器就是以此作为是否拦截的依据。
-
org.apache.ibatis.plugin.Plugin#getAllInterfaces
方法,此方法用于获取被代理类满足拦截条件的父接口。其中此方法是通过org.apache.ibatis.plugin.Plugin#getSignatureMap
返回值作为判断的依据进行接口的筛选。 -
通过2中获取到的被代理类满足拦截条件的父接口数组,
- 如果此数组为空,则被代理对象不需要进行拦截处理,那么直接返回原对象就可以.
return target;
- 如果数组不为空,则此代理对象需要进行拦截处理,那么则生成并返回代理对象。
return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
至此Executor,StatementHandler,parameterHandler,ResultSetHandler代理对象的创建过程就结束了
代理链拦截处理
当代理对象的方法被调用是,则会触发invoke方法,此时invoke
方中通过signatureMap
来判断此类和方法是否满足拦截要求
-
如果不满足要求,则直接执行方法
return method.invoke(target, args);
-
如果满足要求则执行拦截器方法,并将当前被代理对象,方法, 参数值传递给拦截器
Set<Method> methods = signatureMap.get(method.getDeclaringClass()); if (methods != null && methods.contains(method)) { return interceptor.intercept(new Invocation(target, method, args)); }
intercpet执行到最后一步,调用Invocation实例的
proceed
方法进行此责任链的下一层继续进行拦截,直到责任链走完(实现类的intercpet需要显示的调用Invocation的proceed方法)。