插件简介

一般情况下,开源框架都会提供插件或者扩展点,供开发者自行拓展。这样的好处显而易见,一是增加了框架的灵活性,二是开发者结合实际需求,对框架进行扩展,使它能够更好的工作。

以 MyBatis 为例子,我们可以基于 MyBatis 插件机制实现分页、分表、监控功能。由于插件和业务无关,业务也无法感知插件的存在,因此可以无感植入插件,在无形中实现增强。

MyBatis 插件机制允许用户通过实现插件接口来扩展 MyBatis 的功能,拦截 SQL 语句的执行过程,进行自定义的处理。

基本概念

MyBatis 插件机制基于 Java 的拦截器模式,通过插件类实现对 MyBatis 核心行为的拦截和修改。插件可以在执行特定的操作时(如查询、更新、删除等)插入自定义代码。

作用范围

插件的作用对象是 MyBatis 内部的核心组件,如 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler:

  • Executor:执行 SQL 的操作
  • StatementHandler:处理 SQL 语句的生成、参数设置等
  • ResultSetHandler:处理结果集的映射
  • ParameterHandler:处理 SQL 语句的参数设置

常见场景

  • SQL 日志记录
  • SQL 性能监控
  • 事务控制
  • 缓存
  • SQL 改写

优点

  • 灵活性:允许开发者在不修改 MyBatis 核心代码的情况下灵活地增加或修改功能
  • 扩展性:可以轻松地扩展 MyBatis,满足各种业务需求
  • 可定制性:可以根据业务需求定制插件

缺点

  • 调试复杂:由于插件是通过代理模式实现的,调试时可能会增加复杂性
  • 性能开销:每次 SQL 执行时都需要经过插件的拦截和处理

插件介绍

MyBatis 作为一个广泛的优秀的 ORM 开源框架,在四大组件:Executor、StatementHandler、ParameterHandler、ResultSetHandler,提供了简易易用的插件扩展机制。

MyBatis 允许拦截的方法如下:

  • 执行器 Executor:update, query, commit, rollback 等等
  • SQL 语法构建器 StatementHandler:prepare、parameterize、batch、updates query 等方法
  • 参数处理器 ParameterHandler:getParameterObject, setParameters 方法
  • 结果集处理器 ResultSetHandler:handlerResultSets, handleOutputParameters 等方法

插件原理

在四大核心对象创建的时候,每个创建出来都不是直接返回的,而是 interceptorChain.pluginAll(parameterHandler)

public ParameterHandler createParameterHandler(
        MappedStatement mappedStatement,
        Object parameter,
        BoundSql boundSql,
        InterceptorChain interceptorChain) {

    ParameterHandler parameterHandler =
            mappedStatement.getLang().createParameterHandler(mappedStatement, parameter, boundSql);

    parameterHandler = (ParameterHandler) interceptorChain.applyInterceptors(parameterHandler);

    return parameterHandler;
}

public Object applyInterceptors(Object target) {
    for (Interceptor interceptor : interceptors) {
        target = interceptor.plugin(target);
    }
    return target;
}

interceptorChain 保存了所有的拦截器,调用拦截器链中的拦截器依次对目标进行拦截或增强。

自定义插件

MyBatis 插件接口 Interceptor 包含三个方法:

  • interceptor 方法:插件的核心方法
  • plugin 方法:生成 target 的代理对象
  • setProperties 方法:传递插件所需参数
@Intercepts({
    @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
    )
})
public class ExamplePlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

配置插件:

<plugins>
    <plugin interceptor="icu.wzk.interceptor.ExamplePlugin"></plugin>
</plugins>

源码分析

Plugin 实现了 InvocationHandler 接口,invoke 方法会对所有拦截的方法进行检测:

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

Invocation 类用于存储目标类、方法以及方法参数列表:

public class Invocation {
    private final Object target;
    private final Method method;
    private final Object[] args;

    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }
}