全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-20 8 分钟 ✍️ juanwangdev

插件拦截原理

MyBatis 插件体系基于责任链模式和 JDK 动态代理实现,通过拦截四大核心对象的方法调用,在 SQL 执行链路中注入自定义逻辑。

四大核心拦截对象

MyBatis SQL 执行链路中有四个可被拦截的核心对象,按执行顺序排列:

Java
Executor → StatementHandler → ParameterHandler → ResultSetHandler
拦截对象职责可拦截方法典型应用
Executor执行器,调度 SQL 执行query, update, commit, rollback分页插件、缓存插件、读写分离
StatementHandler创建并执行 SQL 语句prepare, parameterize, query, updateSQL 改写、性能监控、路由分库
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);
    }
}

四大对象的代理创建时机

对象创建时机所在方法
ExecutorConfiguration.newExecutor()创建 SqlSession 时
StatementHandlerConfiguration.newStatementHandler()Executor.query/update 内部
ParameterHandlerConfiguration.newParameterHandler()StatementHandler 构造时
ResultSetHandlerConfiguration.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 必须与目标方法的参数列表完全一致(类型、数量、顺序),否则无法匹配拦截。

注意事项

  1. 方法签名匹配@Signatureargs 必须与目标方法签名完全一致,类型错误将导致拦截失效
  2. 必须调用 proceed:除非明确要跳过原始方法(如短路缓存),否则必须调用 invocation.proceed()
  3. 代理链顺序:插件在 XML 中的声明顺序决定拦截顺序,需注意依赖关系
  4. 性能影响:每个插件增加一层代理和调用开销,生产环境不宜过多
  5. 类型判断plugin() 方法中应先判断目标类型是否需要拦截,避免创建无效代理

要点总结

  • MyBatis 四大拦截对象按执行顺序为:Executor → StatementHandler → ParameterHandler → ResultSetHandler
  • 拦截基于 JDK 动态代理,Plugin.wrap() 判断目标类型匹配后创建代理
  • Plugin.invoke() 中根据 @Signature 匹配决定是否执行拦截器逻辑
  • 多插件形成拦截链,顺序由 XML 声明顺序决定,类似 AOP 环绕通知
  • 每个拦截对象在 Configuration.newXxxHandler() 时通过 interceptorChain.pluginAll() 应用所有插件
  • @Signatureargs 必须与目标方法参数完全一致,否则拦截失效

存放路径:D:\git2\jwdev\articles\MYBATIS\进阶\插件机制\插件拦截原理.md

📝 发现内容有误?点击此处直接编辑

← 上一篇 性能监控插件
下一篇 → JSON 类型处理
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库