前言

插件增加了框架的灵活性,使得我们可以根据需要自行对功能进行拓展。在MyBatis中,插件就类似于拦截器,通过拦截相应的方法从而执行插件逻辑。MyBatis所允许拦截的接口与方法如下:

  • Executor
  • ParameterHandler
  • ResultSetHandler
  • StatementHandler

插件配置

如果我们要实现一个插件,比如我们想要拦截Executorquery方法,那么可以这样定义插件:

1
2
3
4
5
6
7
8
9
10
11
12
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
),
@Signature(...),
@Signature(...)
})
public class ExamplePlugin implements Interceptor {
// 省略逻辑
}

这里的注解是必须的,@Intercepts注解装载一个@Signature列表,一个@Signature其实就是一个需要拦截的方法封装。那么,当一个拦截器要拦截多个方法,自然就是一个@Signature列表。

除此之外,我们还需将插件配置到相关文件中,这样MyBatis在启动时可以加载插件,并保存插件实例到拦截器链InterceptorChain中。待准备工作做完后,MyBatis处于就绪状态。我们在执行SQL时,需要先通过DefaultSqlSessionFactory创建SqlSessionExecutor实例会在创建SqlSession的过程中被创建,Executor实例创建完毕后,MyBatis会通过JDK动态代理为实例生成代理类。这样,插件逻辑即可在Executor相关方法被调用前执行。配置示例如下:

1
2
3
4
5
6
7
8
9
<?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.mybatis3.interceptor.ExamplePlugin">
// ...
</plugin>
</plugins>
</configuration>

注册插件

Executor实例是在开启SqlSession时被创建的,Executor的创建过程封装在Configuration中,注册插件也正是这个时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;

// 根据 executorType 创建相应的 Executor 实例
if (ExecutorType.BATCH == executorType) {...}
else if (ExecutorType.REUSE == executorType) {...}
else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}

// 注册插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

在上面创建好Executor后,紧接着通过拦截器链interceptorChainExecutor实例注册代理逻辑(注意是注册而非执行):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

// ...
}

它的pluginAll()方法会调用具体插件的plugin()方法注册相应的插件逻辑。如果有多个插件,则会多次调用plugin()方法,最终生成一个层层嵌套的代理类。plugin()方法是由具体的插件类实现,以下是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@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);
}

// ...
}

public class Plugin implements InvocationHandler {

public static Object wrap(Object target, Interceptor interceptor) {
// 获取插件类 @Signature 注解内容
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 获取目标类实现的接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 通过 JDK 动态代理为目标类生成代理类
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}

// ...
}

如上,plugin方法在内部调用了Plugin类的wrap()方法,用于为目标对象生成代理。Plugin类实现了InvocationHandler接口,因此它可以作为参数传给ProxynewProxyInstance()方法。

执行插件

在上面注册插件的过程中,我们在wrap()方法中看到了如下代码,它传入了一个Plugin对象作为参数:

1
2
3
4
5
// 通过 JDK 动态代理为目标类生成代理类
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));

Plugin实现了InvocationHandler接口,因此它的invoke()方法会拦截所有的方法调用。invoke()方法会对所拦截的方法进行检测,以决定是否执行插件逻辑。该方法的逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Plugin implements InvocationHandler {

private final Object target;
private final Interceptor interceptor;
private final Map<Class<?>, Set<Method>> signatureMap;

// ...

@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)) {
// 执行插件逻辑,在 ExamplePlugin 实现中仅仅为执行被拦截的方法
return interceptor.intercept(new Invocation(target, method, args));
}
// 执行被拦截的方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}

// ...

}

参考资料