Mybatis 源码解析(四) Mybatis 拦截器实现与解析

本文深入解析Mybatis拦截器的实现原理,包括Interceptor接口、plugin方法、拦截器链的创建与执行逻辑。通过分析,展示了如何在Mybatis中对ParameterHandler、ResultSetHandler、StatementHandler和Executor进行拦截,并探讨了拦截器在时间统计、日志打印、性能分析等场景的应用。同时,文章指出Mybatis的拦截器实现不同于传统拦截器,它直接利用JDK动态代理实现拦截器链。
摘要由CSDN通过智能技术生成

首先从代码包里面看到有一个org.apache.ibatis.plugins的包,猜想就是这里可以进行扩展了,可以看到刚好有Interceptor这个扩展点,那就先从Interceptor入手。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZFGihKyo-1573975346688)(17D7AB5BDE6B4B2DB5C68BBB4366EF96)]

无论实在JavaWeb当中的Filter,还是在Spring里面的HandlerInterceptor都可以或多或少的知道这个责任链模式,如果没有相关知识储备的,请自行查阅。

我们在这里可以看到有一个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);
  }

}

就是一个使用List存储拦截器的一个数据结构,这样看来这个只是一个存储结构,相应的执行逻辑并不在其中。

我们在寻址找到拦截器的定义。

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
  }

}

可以看到这里面有两个方法

  1. intercept 这里是真正执行拦截的逻辑,里面封装一个Invocation的上下文,这里面包含了被拦截对象、被拦截方法、参数,就是一个java正常反射该有的东西。
  2. plugin 方法,这个就是创建target的代理。如果被包装对象是包含实现的接口的话,那么就使用jdk的动态代理方式创建一个代理实例。当然如果不是interface,那么就无能为例了,所以这里也预留了扩展,如果需要的话,可以自己扩展,当然这里主要是对MapperInterface进行拦截,所以大部分情况下,我们不需要自己扩展。

现在回过头来,再看上面的InterceptorChain#pluginAll就是为当前的target创建一个代理链,在执行真正的方法之前或之后,会把链执行一遍。

那么找到对应的调用逻辑,就知道具体拦截的是什么了,但是从这里我们也可以知道,这个plugin的设计思路也可以用在我们的日常代码开发之中,比如时间统计、日志打印、性能分析等。

抛开以上题外话,继续了解我们的mybatis是怎么使用这个plugin的?具体是在哪里可以作为扩展点拦截呢?

我们经过溯源发现,这个拦截器链实在Configuration里面进行配置的。

  protected final InterceptorChain interceptorChain = new InterceptorChain();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SCD0wCb8-1573975346689)(560340CEAFF0422694BAB1E9C8D67595)]

可以看到有四个地方用到了。

  • ParameterHandler 参数处理器
  • ResultSetHandler 结果处理器
  • StatementHandler 过程处理器
  • Executor 执行器处理器

所以我们可以对这几个都可以进行拦截。

意味这样就完了吗?

不是的,由于这样的原因,有一点没有讲到,就是我们上面可以看到有四种类型的被拦截方法,肯定不会说是一个Interceptor对这个四种的每一种都生效,那么是如何实现的匹配呢?

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
  Signature[] value();
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
  Class<?> type();

  String method();

  Class<?>[] args();
}

有一个注解来声明具体的方法签名,可以看到type,method,args!!!

再看一下Plugin.wrap的详细逻辑

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

  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
    }
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
      try {
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }
  
  // 获取当前类的所有接口,
  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<>();
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }

大概是这么一个过程

  1. 获取拦截器上面的注解Interceptors,然后拿到上面声明的被拦截方法签名,然后获取到被拦截的具体方法(找到配置的谁被拦截)
  2. 继续获取到target上面的接口有多少是可以被拦截到的(找出实际是谁被拦截)
  3. 然后把被拦截的接口进行增强。(为实际的进行代理)

如下,是对ParameterHandlersetParameters方法进行增强。

@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
})
public class ParameterHandlerInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object o = invocation.getTarget();
        //System.out.println(JSONUtils.toJSONString(invocation));
        System.out.println("==============ParameterHandlerInterceptor===================");
        return invocation.proceed();
    }

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

    @Override
    public void setProperties(Properties properties) {

    }
}

所以我们知道了,在Myabtis的拦截器实现里面,主要有几个元素

  1. 拦截器
  2. 拦截器链
  3. 拦截器定义
  4. 拦截器链工具类:以方法签名为条件的进行增强

这里和其它的拦截器稍微有些不一样的在于他是直接使用JDK代理实现了这个链的方式。

除此之外,还可以使用循环执行、链表的方式实现拦截器链。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值