mybatis-插件体系之原理
Posted siye1989
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis-插件体系之原理相关的知识,希望对你有一定的参考价值。
1. 概述
本文,我们来分享 MyBatis 的插件模块,对应 plugin
包。如下图所示:
在 《精尽 MyBatis 源码解析 —— 项目结构一览》 中,简单介绍了这个模块如下:
Mybatis 自身的功能虽然强大,但是并不能完美切合所有的应用场景,因此 MyBatis 提供了插件接口,我们可以通过添加用户自定义插件的方式对 MyBatis 进行扩展。用户自定义插件也可以改变 Mybatis 的默认行为,例如,我们可以拦截 SQL 语句并对其进行重写。
由于用户自定义插件会影响 MyBatis 的核心行为,在使用自定义插件之前,开发人员需要了解 MyBatis 内部的原理,这样才能编写出安全、高效的插件。
- 总的来说,MyBatis 的插件,还是基于动态代理来实现。所以,?? 胖友有没发现,动态代理,无处不在。嘿嘿。
- 另外,胖友先看看 《MyBatis 官方文档 —— 插件》 文档,对 MyBatis 插件的概念和配置,再有一个了解。
本文涉及的类如下图所示:
下面,我们逐个类来瞅瞅。
2. Interceptor
org.apache.ibatis.plugin.Interceptor
,拦截器接口。代码如下:
// Interceptor.java
|
- 关于三个方法,胖友看看代码注释。
2.1 示例
我们来看看 org.apache.ibatis.plugin.PluginTest
,提供了 AlwaysMapPlugin 示例。代码如下:
|
<1>
处,通过@Intercepts
和@Signature
注解,定义了需要拦截的方法为 Map 类型、方法为"get"
方法,方法参数为Object.class
。详细解析,见 TODO。<2>
处,在实现方法#plugin(Object target)
方法内部,他调用Plugin#wrap(Object target, Interceptor interceptor)
方法,执行代理对象的创建。详细解析,见 TODO。<3>
处,在实现方法#setProperties(Properties properties)
方法内部,暂未做任何实现。此处可以实现,若 AlwaysMapPlugin 有属性,可以从properties
获取一些需要的属性值。<4>
处,在实现方法#intercept(Invocation invocation)
方法,直接返回"Always"
字符串。也就是说,当所有的target
类型为 Map 类型,并且调用Map#get(Object)
方法时,返回的都是"Always"
。
这个示例,胖友可以跑下 PluginTest 单元测试里的方法,可以更加好理解。
2.2 Invocation
org.apache.ibatis.plugin.Invocation
,方法调用信息,作为 Interceptor#intercept(Invocation invocation)
的方法参数。代码如下:
// Invocation.java
|
3. InterceptorChain
org.apache.ibatis.plugin.InterceptorChain
,拦截器 Interceptor 链。
3.1 构造方法
// InterceptorChain.java
|
3.2 addInterceptor
#addInterceptor(Interceptor interceptor)
方法,添加拦截器。代码如下:
// InterceptorChain.java
|
该方法在 Configuration 的 #pluginElement(XNode parent)
方法中被调用,代码如下:
// XMLConfigBuilder.java
|
<1>
处,创建 Interceptor 对象,并调用Interceptor#setProperties(properties)
方法,设置拦截器的属性。-
<2>
处,调用Configuration#addInterceptor(interceptorInstance)
方法,添加到Configuration.interceptorChain
中。代码如下:// Configuration.java
/**
* 拦截器链
*/
protected final InterceptorChain interceptorChain = new InterceptorChain();
public void addInterceptor(Interceptor interceptor)
interceptorChain.addInterceptor(interceptor);
3.3 pluginAll
#pluginAll(Object target)
方法,应用所有拦截器到指定目标对象。代码如下:
// InterceptorChain.java
|
该方法被 Configuration 的如下四处方法中调用:
- 一共可以有四种目标对象类型可以被拦截:1)Executor;2)StatementHandler;3)ParameterHandler;4)ResultSetHandler 。
4. @Intercepts
org.apache.ibatis.plugin.@Intercepts
,拦截器注解。代码如下:
// Intercepts.java
|
4.1 @Signature
org.apache.ibatis.plugin.@Signature
,方法签名的注解。代码如下:
// Signature.java
|
5. Plugin
org.apache.ibatis.plugin.Plugin
,实现 InvocationHandler 接口,插件类,一方面提供创建动态代理对象的方法,另一方面实现对指定类的指定方法的拦截处理。
注意,Plugin 是 MyBatis 插件体系的核心类。
5.1 构造方法
// Plugin.java
|
5.2 wrap
#wrap(Object target, Interceptor interceptor)
静态方法,创建目标类的代理对象。代码如下:
// Plugin.java
|
-
<1>
处,调用#getSignatureMap(Interceptor interceptor)
方法,获得拦截的方法映射。代码如下:// Plugin.java
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor)
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null)
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs)
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
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);
return signatureMap;- 基于
@Intercepts
和@Signature
注解。
- 基于
-
<2>
处,调用#getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap)
方法,获得目标类的接口集合。代码如下:// Plugin.java
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap)
// 接口的集合
Set<Class<?>> interfaces = new HashSet<>();
// 循环递归 type 类,机器父类
while (type != null)
// 遍历接口集合,若在 signatureMap 中,则添加到 interfaces 中
for (Class<?> c : type.getInterfaces())
if (signatureMap.containsKey(c))
interfaces.add(c);
// 获得父类
type = type.getSuperclass();
// 创建接口的数组
return interfaces.toArray(new Class<?>[interfaces.size()]);- 从这里可以看出,
@Signature
注解的type
属性,必须是接口。
- 从这里可以看出,
<3.1>
处,情况一,若有接口,则创建目标对象的 JDK Proxy 对象。<3.2>
处,情况二,若无接口,则返回原始的目标对象。
5.3 invoke
// Plugin.java
|
以上是关于mybatis-插件体系之原理的主要内容,如果未能解决你的问题,请参考以下文章
182. Spring Boot MyBatis插件之拦截器(Interceptor)实现原理