mybatis插件源码理解
最在使用mybatis,对这个插件的原理比较好奇,分享下自己的理解。
官方文档 https://mybatis.org/mybatis-3/zh/configuration.html#plugins
我就按照自己的理解按照2个部分,一个是配置解析的部分和调用的部分。
解析过程:
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
以上图配置为例
解析过程比较简单,创建SqlSessionFactory时开始解析:
首先解析plugins的标签,然后解析里面的子标签plugin,然后解析interceptor标签, 在解析property。 根据interceptor和property创建插件对象。让后放入到全局配置变量configuration对象的interceptorChain属性中(就是个ArrayList)。
代码如下
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历 <plugins /> 标签
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
// 创建 Interceptor 对象,并设置属性
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
// 添加到 configuration 中
configuration.addInterceptor(interceptorInstance);
}
}
}
调用过程:
在调用openSession时(生成sqlSessionFactory时)会创建executor, 此时会遍历configuration中的interceptorChain中的所有interceptor,根据遍历出来的interceptor为当前创建executor(利用动态代理)包装多层代理对象Plugin(这个plugin包涵interceptor信息)。
public static Object wrap(Object target, Interceptor interceptor) {
// 获得拦截的方法映射
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 获得目标类的类型
Class<?> type = target.getClass();
// 获得目标类的接口集合
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 若有接口,则创建目标对象的 JDK Proxy 对象
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap)); // 因为 Plugin 实现了 InvocationHandler 接口,所以可以作为 JDK 动态代理的调用处理器
}
// 如果没有,则返回原始的目标对象
return target;
}
所以在调用executor curd的时候,实际上调用的是做外层Plugin的invoke方法, 这个invoke会相判断当前的调用方法是否需要拦截,是就调用interceptor的intercept方法,不是就调用原方法。
如此一层一层调用下去(套娃)。
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);
}
}
此外:在mybatis在掉用我们的插件的intercept方法时,会传递个Invocation对象,这个对象包含了,target 包了代理对象的executor, 当前调用method的方法,args参数。这个Invocation对象包涵了一个proceed方法,用来直接调用method。
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
我的理解这个插件就是个套娃。。。。 如有错误欢迎指正。