插件拦截原理
MyBatis 插件体系基于责任链模式和 JDK 动态代理实现,通过拦截四大核心对象的方法调用,在 SQL 执行链路中注入自定义逻辑。
四大核心拦截对象
MyBatis SQL 执行链路中有四个可被拦截的核心对象,按执行顺序排列:
Java
Executor → StatementHandler → ParameterHandler → ResultSetHandler
| 拦截对象 | 职责 | 可拦截方法 | 典型应用 |
|---|---|---|---|
Executor | 执行器,调度 SQL 执行 | query, update, commit, rollback | 分页插件、缓存插件、读写分离 |
StatementHandler | 创建并执行 SQL 语句 | prepare, parameterize, query, update | SQL 改写、性能监控、路由分库 |
ParameterHandler | 处理 SQL 参数绑定 | getParameterObject, setParameters | 参数加密/脱敏、自定义类型处理 |
ResultSetHandler | 处理结果集映射 | handleResultSets, handleOutputParameters | 结果集加密解密、动态字段映射 |
执行链路详解
Java
1. SqlSession.selectList()
↓
2. Executor.query(MappedStatement)
← 可拦截点:改写 SQL、添加缓存、分页
↓
3. StatementHandler.prepare(Connection)
← 可拦截点:SQL 最终改写、性能监控开始
↓
4. StatementHandler.parameterize(PreparedStatement)
↓
5. ParameterHandler.setParameters(PreparedStatement)
← 可拦截点:参数加密、类型转换
↓
6. PreparedStatement.executeQuery()
↓
7. ResultSetHandler.handleResultSets(Statement)
← 可拦截点:结果集解密、字段增强
↓
8. 返回 List<T>
代理机制
MyBatis 插件基于 JDK 动态代理 实现拦截,核心类为 Plugin。
Plugin.wrap() 原理
Java
public static Object wrap(Object target, Interceptor interceptor) {
// 1. 获取拦截器声明的 @Signature 信息
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 2. 判断目标类型是否在拦截范围内
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 3. 如果匹配,创建代理;否则返回原对象
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
关键点:
- 通过
@Intercepts和@Signature注解声明要拦截的对象类型+方法 - 只有目标对象类型匹配时才创建代理,否则直接返回原对象(零侵入)
- 代理对象实现目标对象的所有接口,方法调用被路由到
Plugin.invoke()
Plugin.invoke() 执行流程
Java
@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)) {
return interceptor.intercept(new Invocation(target, method, args));
}
// 不在拦截范围,直接调用原方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
拦截链(责任链模式)
多个插件可同时拦截同一对象,形成拦截链。
链式构建
Java
// Configuration 中插件注册
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
// 创建代理时遍历所有拦截器
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
拦截链执行顺序
Java
目标方法调用
↓
┌─ Interceptor A ─┐
│ before A │
│ ↓ │
│ ┌─ Interceptor B─┐
│ │ before B │
│ │ ↓ │
│ │ 原始方法执行 │
│ │ ↓ │
│ │ after B │
│ └────────────────┘
│ ↓ │
│ after A │
└─────────────────┘
↓
返回结果
拦截顺序由
mybatis-config.xml中<plugin>的声明顺序决定,先注册的插件先拦截、后返回结果(类似 AOP 的环绕通知)。
多层代理注意事项
Java
Executor 代理 → Executor 代理 → ... → 原始 Executor
(插件A) (插件B)
- 每个插件对同一目标创建一层代理,形成代理嵌套
invocation.proceed()调用链中下一层代理或原始方法- 过多插件叠加会导致代理链过深,影响性能
- 某个插件忘记调用
proceed()会导致后续插件和原始方法全部跳过
InterceptorChain 源码分析
text
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
四大对象的代理创建时机
| 对象 | 创建时机 | 所在方法 |
|---|---|---|
Executor | Configuration.newExecutor() | 创建 SqlSession 时 |
StatementHandler | Configuration.newStatementHandler() | Executor.query/update 内部 |
ParameterHandler | Configuration.newParameterHandler() | StatementHandler 构造时 |
ResultSetHandler | Configuration.newResultSetHandler() | StatementHandler 构造时 |
text
// Executor 创建示例(Configuration.java)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
Executor executor;
// 根据类型创建具体 Executor
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 如果开启缓存,包装为 CachingExecutor
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// ★ 应用所有插件代理
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
自定义拦截器编写
标准模板
text
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update",
args = {MappedStatement.class, Object.class})
})
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 前置处理
before(invocation);
// 执行原始方法(或跳过)
Object result = invocation.proceed();
// 后置处理
after(result);
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 读取插件配置
}
}
@Signature 注解详解
text
@Signature(
type = Executor.class, // 拦截对象类型
method = "query", // 拦截方法名
args = { // 方法参数类型列表(用于方法重载区分)
MappedStatement.class,
Object.class,
RowBounds.class,
ResultHandler.class
}
)
args必须与目标方法的参数列表完全一致(类型、数量、顺序),否则无法匹配拦截。
注意事项
- 方法签名匹配:
@Signature的args必须与目标方法签名完全一致,类型错误将导致拦截失效- 必须调用 proceed:除非明确要跳过原始方法(如短路缓存),否则必须调用
invocation.proceed()- 代理链顺序:插件在 XML 中的声明顺序决定拦截顺序,需注意依赖关系
- 性能影响:每个插件增加一层代理和调用开销,生产环境不宜过多
- 类型判断:
plugin()方法中应先判断目标类型是否需要拦截,避免创建无效代理
要点总结
- MyBatis 四大拦截对象按执行顺序为:Executor → StatementHandler → ParameterHandler → ResultSetHandler
- 拦截基于 JDK 动态代理,
Plugin.wrap()判断目标类型匹配后创建代理 Plugin.invoke()中根据@Signature匹配决定是否执行拦截器逻辑- 多插件形成拦截链,顺序由 XML 声明顺序决定,类似 AOP 环绕通知
- 每个拦截对象在
Configuration.newXxxHandler()时通过interceptorChain.pluginAll()应用所有插件 @Signature的args必须与目标方法参数完全一致,否则拦截失效
存放路径:D:\git2\jwdev\articles\MYBATIS\进阶\插件机制\插件拦截原理.md
📝 发现内容有误?点击此处直接编辑