从Mybatis源码分析插件原理

本文深入剖析了MyBatis的插件机制,通过源码解析展示了如何拦截Executor、ParameterHandler、ResultSetHandler和StatementHandler等关键组件的方法。插件的实现基于责任链模式和动态代理,允许开发者在执行过程中插入自定义逻辑,如LogInterceptor2示例所示,用于记录查询语句的执行耗时。通过Plugin.wrap方法,MyBatis创建了代理对象,确保每个插件调用能按序执行,形成插件链。了解这一机制有助于自定义更复杂的MyBatis插件。

插件(plugins)

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

源码分析

mybatis的插件机制也是一种非常好的设计,扩展性很强,可以基于插件做很多事,比如PageHelper就是很好的使用了插件机制来实现分页

以Executor的插件为例,创建executor的时候有这样一段代码:

executor = (Executor) interceptorChain.pluginAll(executor);
public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);
  }
  return target;
}

也就是调用了所有插件的plugin方法,并且传入什么类型的对象,返回的也是相同类型对象,层层包装,而这个plugin方法是需要我们实现的,我们的实现是怎么样的那么逻辑就是怎么样的

以自定义一个最简单的自定义插件为例,这个插件的作用就是拦截所有查询语句,所以拦截的是Executor的query方法,然后打印所有查询语句的耗时

@Intercepts(
        {
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        }
)
public class LogInterceptor2 implements Interceptor {

    private Properties properties;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long time1 = System.currentTimeMillis();
        Object result = invocation.proceed();
        long time2 = System.currentTimeMillis();
        System.out.println("执行完成耗时:" + (time2 - time1) + "ms");
        return result;
    }

    @Override
    public Object plugin(Object target) {
        /**
         * 一般使用mybatis自带生成动态代理方法wrap即可
         */
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

Plugin.wrap是Mybatis提供的,看下实现

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

核心代码是jdk的动态代理,每次都包装了一个Plugin对象生成一个代理对象,Plugin就是实现了jdk的InvocationHandler对象,所有每次对象被调用都会进入Plugin的invoke方法

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

interceptor.intercept 又回调到插件类的intercept 方法了,我们实现的intercept一般都需要调用invocation.proceed(),否则插件的流程链就被我们中断了

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

调用了被代理对象的方法,此时如果被代理对象仍然是一个插件的动态代理对象,那么就依次形成了一个插件链,直到执行到最后一个原生的Executor对象

总结

这就是插件的实现原理了,通过源码分析,可以看到是插件原理是基于责任链模式+动态代理模式,这样的设计还是非常巧妙的

简单流程:

调用Executor的query -> Executor是一个动态代理类,所以调用的Plugin的invoke方法 -> 调用到插件类的intercept方法 -> invocation.proceed() -> 调用被代理对象的方法,此时如果被代理对象仍然是一个插件的动态代理对象,那么就依次形成了一个插件链,直到执行到最后一个原生的Executor对象

需要实现自己的自定义插件的话可以看这篇:https://blog.csdn.net/qq_31086797/article/details/114446002

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值