MyBatis 的插件机制是其核心扩展能力之一,通过动态代理和拦截器模型,允许开发者在 SQL 执行的关键节点插入自定义逻辑(如日志记录、性能监控、SQL 改写等)。以下是其核心原理和实现细节的深度解析:
一、插件运行原理的核心架构
-
拦截器接口(Interceptor)
• 定义:插件需实现Interceptor接口,包含三个核心方法:◦intercept(Invocation invocation):拦截目标方法,执行自定义逻辑。
◦plugin(Object target):生成代理对象,包装目标对象。
◦setProperties(Properties properties):设置插件属性(通过 XML 配置传递参数)。
• 作用:拦截器是插件的核心逻辑载体,决定何时触发拦截及如何处理。
-
动态代理机制
• 代理对象生成:MyBatis 通过 Java 动态代理(Proxy.newProxyInstance)为被拦截的核心组件(如Executor、StatementHandler)生成代理类。• 方法拦截:当调用代理对象的方法时,会触发InvocationHandler的invoke方法,进而执行插件的intercept方法。
- 插件链(Interceptor Chain)
• 多插件协作:多个插件可按配置顺序拦截同一方法,形成链式调用。每个插件的intercept方法依次执行,最终调用invocation.proceed()传递到下一个插件或原始方法。
二、插件执行流程详解
-
插件注册
• 配置方式:在mybatis-config.xml中通过<plugins>标签注册插件,或使用@Intercepts注解声明拦截目标。• 示例配置:
1
2
3
4
5
<
plugins
>
<
plugin
interceptor
=
"com.example.LoggingInterceptor"
>
<
property
name
=
"logLevel"
value
=
"DEBUG"
/>
</
plugin
>
</
plugins
>
-
目标对象包装
• 代理生成时机:MyBatis 初始化时扫描所有插件,根据@Signature注解匹配目标类和方法,生成代理对象。• 核心组件拦截:插件可作用于以下四个核心接口:
◦Executor:SQL 执行器(如update、query方法)。
◦StatementHandler:SQL 语句处理器(如预编译、参数绑定)。
◦ParameterHandler:参数处理器(参数设置)。
◦ResultSetHandler:结果集处理器(结果映射)。
-
方法拦截与逻辑增强
• 拦截点选择:通过@Signature注解指定拦截目标:1
2
3
@Intercepts
({
@Signature
(type = Executor.
class
, method =
"update"
, args = {MappedStatement.
class
, Object.
class
})
})
• 逻辑插入:在intercept方法中,可通过invocation.getArgs()获取方法参数,通过invocation.proceed()调用原始方法,实现日志记录、SQL 改写等操作。
三、插件开发示例
-
日志记录插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Intercepts
({
@Signature
(type = Executor.
class
, method =
"update"
, args = {MappedStatement.
class
, Object.
class
})
})
public
class
SqlLogInterceptor
implements
Interceptor {
@Override
public
Object intercept(Invocation invocation)
throws
Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[
0
];
Object parameter = invocation.getArgs()[
1
];
String sql = ms.getBoundSql(parameter).getSql();
System.out.println(
"Executing SQL: "
+ sql);
long
start = System.currentTimeMillis();
Object result = invocation.proceed();
System.out.println(
"Execution time: "
+ (System.currentTimeMillis() - start) +
"ms"
);
return
result;
}
@Override
public
Object plugin(Object target) {
return
Plugin.wrap(target,
this
);
}
}
-
分页插件(简化版)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Intercepts
({
@Signature
(type = StatementHandler.
class
, method =
"prepare"
, args = {Connection.
class
, Integer.
class
})
})
public
class
PaginationInterceptor
implements
Interceptor {
@Override
public
Object intercept(Invocation invocation)
throws
Throwable {
StatementHandler handler = (StatementHandler) invocation.getTarget();
MappedStatement ms = handler.getMappedStatement();
Object parameter = handler.getParameterHandler().getParameterObject();
// 动态改写 SQL 添加分页逻辑(如 LIMIT/OFFSET)
BoundSql boundSql = handler.getBoundSql();
String originalSql = boundSql.getSql();
String pageSql = originalSql +
" LIMIT 0, 10"
;
// 示例分页
// 通过反射修改 BoundSql 中的 SQL
Field field = boundSql.getClass().getDeclaredField(
"sql"
);
field.setAccessible(
true
);
field.set(boundSql, pageSql);
return
invocation.proceed();
}
}
四、插件应用场景
场景 | 实现方式 | 典型插件 |
---|---|---|
SQL 日志记录 | 拦截Executor.update和query方法 | p6spy、MyBatis-Log4j |
分页查询 | 拦截StatementHandler.prepare方法 | PageHelper |
性能监控 | 统计 SQL 执行时间 | MyBatis-Performance |
数据加密/解密 | 拦截ParameterHandler.setParameters | Jasypt-MyBatis |
自动事务管理 | 拦截Executor.commit/rollback方法 | 自定义事务插件 |