Configuration

可以对下面四个地方进行插件注册,它们都是通过 interceptorChain.pluginAll(…); 来注册插件

image-20230907093526816

InterceptorChain

1
2
3
4
5
6
7
8
9
10
11
12
13
public class InterceptorChain {

private final List<Interceptor> interceptors = new ArrayList<>();

// 会去遍历我们在mybatis中配置的插件
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
.....
}

Interceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Interceptor {

Object intercept(Invocation invocation) throws Throwable;


default Object plugin(Object target) {
// 调用了Plugin的warp方法
// 将target和拦截器对象传了进去
// 所以需要实现这个类(Plugin中可以看出)
return Plugin.wrap(target, this);
}

default void setProperties(Properties properties) {
// NOP
}

}

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public static Object wrap(Object target, Interceptor interceptor) {
// signatureMap是需要拦截的类、方法名字、方法参数
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 目标对象的Class对象
Class<?> type = target.getClass();
// 获取接口Class对象
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 我们拦截的是一个接口
if (interfaces.length > 0) {
// JDK动态代理
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
// 不是接口直接返回目标对象
return target;
}

private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
// 我们实现的插件拦截器需要一个 Intercepts的注解
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());
}
// 这个注解的value是一个 Signature数组并获取
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
// 遍历 Signature 数组
for (Signature sig : sigs) {
// Signature的类型作为signatureMap的key, 如果不存在才会去添加一个新的hashSet
// 返回这个HashSet的引用
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
try {
// 将sig的方法名字,sig的参数都装到Method中
Method method = sig.type().getMethod(sig.method(), sig.args());
// 装进methods中(Set<Method>)
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}

// type: 目标对象的Class对象
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
// 根据刚才 Signature.type(), 判定我们是不是拦截的是一个接口
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
// 拿去目标对象父类的class对象
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}

image-20230907100901014

Plugin 也实现了invoke, 上方的如果我们需要拦截的一个类是一个接口,它就会new 一个Plugin的对象来代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 判定我们是否拦截的method 方法
// 如果拦截了就调用拦截器的intercept方法
// 并且intercept方法会有个Invocation参数
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);
}
}

MyLogInterceptor

自定义拦截器

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


/**
* @author 🍍
* @email 1352955539@qq.com
*/

@Intercepts(
{
@Signature(
type = StatementHandler.class,
method = "query",
args = {Statement.class, ResultHandler.class}
)
}
)
@Slf4j
public class MyLogInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
String sql = statementHandler.getBoundSql().getSql();
log.error("当前sql: {}", sql);
return invocation.proceed();
}
}

将拦截器配置到文件中

1
2
3
<plugins>
<plugin interceptor="interceptor.MyLogInterceptor"></plugin>
</plugins>

结果

image-20230907105937746