返回介绍

7.3 插件的代理和反射设计

发布于 2025-04-26 13:08:34 字数 3332 浏览 0 评论 0 收藏

插件用的是责任链模式。首先什么是责任链模式,就是一个对象,在 MyBatis 中可能是四大对象中的一个,在多个角色中传递,处在传递链上的任何角色都有处理它的机会。这句话还是很抽象,打个比方,你在公司中是个重要人物,你需要请假 3 天。那么,请假流程是,首先你需要项目经理批准,然后部门经理批准,最后总裁批准才能完成。你的请假请求就是一个对象,它经过项目经理、部门经理、总裁多个角色审批处理,每个角色都可以对你的请假请求作出修改和批示。这就是责任链模式,它的作用是让每一个在责任链上的角色都有机会去拦截这个对象。在将来如果有新的角色也可以轻松拦截请求对象,进行处理。

MyBatis 的责任链是由 interceptorChain 去定义的,不知道读者是否记得 MyBatis 在创建执行器时用到过这样的代码。

executor = (Executor) interceptorChain.pluginAll(executor);

我们不妨看看 pluginAll() 方法是如何实现的,如代码清单 7-5 所示。

代码清单 7-5:interceptorChain 中的 pluginAll

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

我们知道 plugin 方法是生成代理对象的方法,当它取出插件的时候是从 Configuration 对象中去取出的。从第一个对象(四大对象中的一个)开始,将对象传递给了 plugin 方法,然后返回一个代理;如果存在第二个插件,那么我们就拿到第一个代理对象,传递给 plugin 方法再返回第一个代理对象的代理......依此类推,有多少个拦截器就生成多少个代理对象。这样每一个插件都可以拦截到真实的对象了。这就好比每一个插件都可以一层层处理被拦截的对象。其实读者只要认真阅读 MyBatis 的源码,就可以发现 MyBatis 的四大对象也是这样处理的。

如果要我们自己编写代理类,工作量会很大,为此 MyBatis 中提供了一个常用的工具类,用来生成代理对象,它便是 Plugin 类。Plugin 类实现了 InvocationHandler 接口,采用的是 JDK 的动态代理,我们先看看这个类的两个十分重要的方法,如代码清单 7-6 所示。

代码清单 7-6:MyBatis 提供生成代理对象的 Plugin 类

public class Plugin implements InvocationHandler {
......
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;
  }

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

我们看到它是一个动态代理对象,其中 wrap 方法为我们生成这个对象的动态代理对象。

我们再看 invoke 方法,如果你使用这个类为插件生成代理对象,那么代理对象在调用方法的时候就会进入到 invoke 方法中。在 invoke 方法中,如果存在签名的拦截方法,插件的 intercept 方法就会被我们在这里调用,然后就返回结果。如果不存在签名方法,那么将直接反射调度我们要执行的方法。

我们创建一个 Invocation 对象,其构造方法的参数包括被代理的对象、方法及其参数。Invocation 对象进行初始化,它有一个 proceed() 方法,如代码清单 7-7 所示。

代码清单 7-7:反射调用被代理对象的 proceed() 方法

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

这个方法就是调度被代理对象的真实方法。现在假设有 n 个插件,我们知道第一个传递的参数是四大对象的本身,然后调用一次 wrap 方法产生第一个代理对象,而这里的反射就是反射四大对象本身的真实方法。如果有第二个插件,我们会将第一个代理对象传递给 wrap 方法,生成第二个代理对象,这里的反射就是指第一个代理对象的 invoke 方法,依此类推直至最后一个代理对象。如果每一个代理对象都调用这个 proceed 方法,那么最后四大对象本身的方法也会被调用,只是它会从最后一个代理对象的 invoke 方法运行到第一个代理对象的 invoke 方法,直至四大对象的真实方法。

在初始化的时候,我们一个个的加载插件实例,并用 setProperties() 方法进行初始化。我们可以使用 MyBatis 提供的 Plugin.wrap 方法去生成代理对象,再一层层地使用 Invocation 对象的 proceed() 方法来推动代理对象运行。所以在多个插件的环境下,调度 proceed() 方法时,MyBatis 总是从最后一个代理对象运行到第一个代理对象,最后是真实被拦截的对象方法被运行。大部分情况下,使用 MyBatis 的 Plugin 类生成代理对象足够我们使用,当然如果你觉得自己可以写规则,也可以不用这个类,我们必须慎之又慎使用这个方法,因为它将覆盖底层的方法。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。