跳至主要內容

Mybatis 源码学习-Plugin 使用

科哒大约 2 分钟

MyBatis 的 Plugin 机制是其扩展性的核心设计之一,通过拦截器(Interceptor)实现对 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler 四大核心组件的动态代理。以下从 使用示例源码分析 两个部分详细说明。


一、Plugin 使用示例

1. 实现自定义拦截器

以下是一个简单的性能监控插件示例,用于统计 SQL 执行时间:

@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 PerformanceInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = invocation.proceed(); // 执行原始方法
        long end = System.currentTimeMillis();
        System.out.println("SQL执行耗时: " + (end - start) + "ms");
        return result;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this); // 生成代理对象
    }

    @Override
    public void setProperties(Properties properties) {
        // 可读取配置文件中的参数
    }
}

2. 注册拦截器

在 MyBatis 配置文件中添加插件:

<plugins>
    <plugin interceptor="com.example.PerformanceInterceptor">
        <!-- 可传递参数 -->
        <property name="threshold" value="100"/>
    </plugin>
</plugins>


二、Plugin 源码解析

1. 拦截器加载过程

MyBatis 在启动时会解析配置文件中的 <plugins> 标签,通过 InterceptorChain 类管理所有拦截器。

关键源码XMLConfigBuilder.java):

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            String interceptor = child.getStringAttribute("interceptor");
            Properties properties = child.getChildrenAsProperties();
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
            interceptorInstance.setProperties(properties);
            configuration.addInterceptor(interceptorInstance);
        }
    }
}

2. 代理对象生成

当创建 Executor 等四大组件时,会调用 InterceptorChain.pluginAll() 方法,对所有拦截器进行嵌套代理。

关键源码InterceptorChain.java):

public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target); // 层层代理
    }
    return target;
}

3. Plugin 类的核心逻辑

Plugin 类通过动态代理(JDK Proxy)实现方法拦截:

public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
        return Proxy.newProxyInstance(
            type.getClassLoader(),
            interfaces,
            new Plugin(target, interceptor, signatureMap));
    }
    return target;
}
  • 动态代理触发:当代理对象的方法被调用时,会进入 Plugin.invoke() 方法。
  • 匹配拦截条件:检查调用的方法是否在 @Signature 注解中声明。

4. 拦截执行流程

代理对象方法调用时,会执行 Interceptor.intercept()

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);
    }
}

三、注意事项

  1. 拦截器顺序:配置文件中声明的拦截器按照“外层代理”顺序执行,即后配置的拦截器先执行。
  2. 性能影响:过度使用拦截器会增加代理链长度,影响性能。
  3. 方法签名匹配@Signaturetypemethodargs 必须与目标方法完全一致。
  4. 仅支持四大组件:只能拦截 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler。

四、总结

MyBatis 的 Plugin 机制通过动态代理和拦截器链实现 AOP 功能,是扩展 MyBatis 的核心手段。理解其源码有助于定制高级功能(如分页、加密等),但需谨慎避免滥用。可通过 Debug 跟踪 InterceptorChainPlugin 类深入验证流程。