聊聊Mybatis中的责任链模式

前言

最近在看mybatis plus的源码,发现org.apache.ibatis.executor这个包路径下的Executor在执行sql的时候会包装成一个责任链,其中这个Interceptor接口就是为了去扩展实现执行sql前后做一些自定义的处理,例如:打印sql信息,并把参数放入sql中进行打印;根据自定义注解加解密数据等。带着个人的兴趣,希望从应用及源码的角度为读者梳理MyBatis的责任链模式。

Interceptor(拦截器)接口

要定义自定义的拦截器的话,需要实现Interceptor接口,Mybatis plus给我们实现了一个MybatisPlusInterceptor类,具体的使用方式以及@Intercepts和@Signature的作用本文不做阐述,我们开始从源码的角度了解mybatis的责任链模式。

以下是Interceptor的源码:

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}

接口中有一个需要实现类实现的拦截方法intercept(Invocation invocation)、默认实现的plugin(Object target)方法,该方法返回的是一个对象、默认实现的一个空方法setProperties(Properties properties)返回值为void。

责任链模式是使多个对象都有机会处理请求,将这些对象连成一条链,这句话就是责任链模式定义的核心。

默认实现是的plugin方法就是将这些对象串起来的核心,所以我们查看Plugin.wrap(target, this)的具体实现,看下具体做了什么事情,为了文章的清晰,省略了后面一部分代码。

wrap方法的核心就是Proxy.newProxyInstance,创建代理对象。

Plugin类实现了JDK的动态代理接口InvocationHandler,所以当前这个类是代理类,代理的对象在调用方法时会进入invoke方法。

Plugin类中的target是目标对象,在mybaits中就是Executor;interceptor就是我们实现的拦截器;signatureMap一个签名map,具体内容和@Intercepts @Signature两个注解有关,有兴趣的可以单独了解。

public class Plugin implements InvocationHandler {

  private final Object target;
  private final Interceptor interceptor;
  private final Map<Class<?>, Set<Method>> signatureMap;

  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

  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);
    }
  }
  
  // 省略后续代码..........
InterceptorChain拦截器链

我们的自定义拦截器需要实现Interceptor接口,同时自定义的拦截器会被Plugin类进行代理,如何把代理对象形成链路,就是InterceptorChain需要做的事情。

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

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

我们根据代码画个图来总结一下链路的生成
在这里插入图片描述

从图中可以知道其实就是代理对象再次生成代理对象,特殊的是代理对象的target属性配合Invocation类中的proceed方法形成链路的调用,这样就可以在我们执行sql的前后,做一些特殊的自定的事情了。

public class Invocation {

  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

  public Object getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

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

}
总结

本文通过mybatis的源码进行了责任链这个设计模式思想的一种学习,对于设计模式的学习,并不是很推荐从生活中的例子来进行学习,比如有的文章从古代三从四德的故事开始讲责任链模式,有点空洞。个人比较喜欢从代码的角度进行设计模式的学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas & Friends

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值