责任链和动态代理的体现 2个月前

如果没有自定义过拦截器, 可以看我前面的文章。如果不知道 JDK 动态代理怎么使用的, 可以看我这文章。 责任链设计模式理解起来很简单, 网上找个例子看看即可。

mybatis 插件的原理使用的是动态代理和责任链来实现的。

1 拦截哪些方法

前面说过, 可以通过注解 InteceptsSignature 来进行指定拦截哪些方法。 然而, 并不是说所有的方法都可以拦截的。

mybatis 拦截器所拦截的方法, 有如下类型:

1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
2. ParameterHandler (getParameterObject, setParameters)
3. ResultSetHandler (handleResultSets, handleOutputParameters)
4. StatementHandler (prepare, parameterize, batch, update, query)

为什么说可以拦截的方法是这些呢?

mybatis 中, 以上几个类是 SqlSession 的四大对象。 SqlSession 通过这些对象实现对数据库的操作, 结果的处理。 因此, 从流程上来说, 是拦截这几个对象中的方法是有非常重要的作用的。 Configuration中的对象 而在源码上的体现呢, 就在 Configuration 类中, 这个类的重要性不做过多的阐述, 可以看看前面的文章。

在总 xml 解析成 Configuration 过程中, 需要 new 出以上的几个类。而以上的几个类在后面都会调用 interceptorChain#pluginAll 方法。

2 如何代理

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

  /**
   * 添加拦截器
   *
   * @param interceptor
   */
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  /**
   * 获取拦截器列表
   *
   * @return
   */
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

可以看到, 其会将调用所有拦截器的 plugin 方法, 层层代理之后返回最终的代理对象。 要注意这里的层层代理。

如果有 A、B、C 三个拦截器(签名相同), 则在此时, 会被层层封装。 最后执行的时候, 是 A>B>C> target.proceed() >C>B>A.

3 代理对象

InterceptorChain 会调用每个拦截器中的 plugin 方法。该方法是会返回相应的代理对象的。

/**
 * 拦截器接口
 *
 * @author Clinton Begin
 */
public interface Interceptor {

  /**
   * 执行拦截逻辑的方法
   *
   * @param invocation 调用信息
   * @return 调用结果
   * @throws Throwable 异常
   */
  Object intercept(Invocation invocation) throws Throwable;

  /**
   * 代理
   *
   * @param target
   * @return
   */
  Object plugin(Object target);

  /**
   * 根据配置来初始化 Interceptor 方法
   * @param properties
   */
  void setProperties(Properties properties);

}

其中的 plugin 是需要我们来实现的。 而 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;
  }

我们在重写 plugin 方法时, 只需要调用上面这个方法即可。 其会返回 Plugin 这个类的一个对象。

public class Plugin implements InvocationHandler
@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 获取方法所在类中, 可以被拦截的所有的方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      // 如果需要被拦截, 则调用 interceptor.intercept
      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);
    }
  }

由于 JDK 的动态代理是接口级别的。 因此, 其代理了类的所有接口的方法。 然而并不是所有的方法都是需要被代理的, 因此, 在方法中通过注解中的签名信息进行区分。

4 责任链设计模式

在插件的使用过程中, 责任链设计模式体现在动态代理的层层嵌套的代理增强之中。 体现在interceptorChain#pluginAll 方法中。 调用时会层层的进行代理。 mybatis 插件的原理-责任链和动态代理的体现

image
EchoEcho官方
无论前方如何,请不要后悔与我相遇。
1377
发布数
439
关注者
2223043
累计阅读

热门教程文档

Python
76小节
Javascript
24小节
Spring Cloud
8小节
React Native
40小节
Docker
62小节