mybatis Interceptor拦截器的执行流程解析

885 阅读3分钟

img_2001079668.jpg

mybatis版本 3.5.9

背景

mybatis-plus的分页插件PaginationInnerInterceptor用了很久,但是之前没有去深究它的实现原理,看了一下是利用mybatis的Interceptor拦截器,所以就先来看看它的执行流程了。

简介

MyBatis Interceptor(拦截器)是 MyBatis 框架提供的一个功能,用于拦截和处理 SQL 语句的执行过程。它允许开发者在 SQL 执行的前后进行一些自定义的操作,例如日志记录、权限校验、性能监控等。

使用 MyBatis Interceptor 需要实现 Interceptor(org.apache.ibatis.plugin.Interceptor) 接口,并重写其中的方法,包括 intercept() 方法和 plugin() 方法。intercept() 方法用于自定义拦截逻辑,plugin() 方法用于包装目标对象,返回一个代理对象,实现拦截器功能。可代理的对象包括:

org.apache.ibatis.executor.Executor

org.apache.ibatis.executor.statement.StatementHandler

org.apache.ibatis.executor.parameter.ParameterHandler

org.apache.ibatis.executor.resultset.ResultSetHandler

Interceptor接口的实现类还需要添加@Intercepts注解,@Intercepts注解是用于声明一个拦截器的注解,它用于标识一个类是一个MyBatis拦截器,并指定该拦截器要拦截的目标对象和方法。以mybatis-plus实现的Interceptor 为例,拦截的是StatementHandler.class子类入参为Connection.class, Integer.class的prepare()。

image.png

执行流程

执行流程主要分:目标对象的包装、调用方法的拦截

2.1 目标对象的包装

1.首先看在org.apache.ibatis.session.Configuration以下几个位置进行包装返回目标对象(如果对象没有在@Intercepts进行配置,则不进行代理)或者代理对象

image.png

image.png 2.进入interceptorChain.pluginAll,可以看到是循环执行interceptor的plugin(target)进行对象包装,这里涉及到mybatis拦截器的排序,越往后的越先执行。

image.png 3.接下来进入interceptor.plugin,接口的默认方法和实现子类的方法差异不大,以MybatisPlusInterceptor为例,也只是进行判断减少代码的执行,具体可看接下来的逻辑

image.png

image.png 4.Plugin.wrap(target, this)就是具体的包装逻辑,重点内容来了。首先看下Plugin的类结构,看到它实现了InvocationHandler接口,通过jdk动态代理的方式生成代理对象,剩下的几个函数也很简单

image.png

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);
    //如果interfaces.length 大于0,则构建Plugin对象并且生成返回代理对象,否则返回目标对象
    //MybatisPlusInterceptor不重写该方法的话返回的结果不变,所以说减少逻辑执行
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
}

//遍历@Intercepts中@Signature列表的配置,返回type:method的map数据结构
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 = MapUtil.computeIfAbsent(signatureMap, 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;
  }
//获取目标对象的实现的接口列表中命中@Signature中type的数组
  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<?>[0]);
  }

5.到这里返回代理对象的逻辑就结束了。

2.2 调用方法的拦截

1.刚才我们看到生成代理对象的逻辑,拦截方法进行功能加强主要放在Plugin对象的Object invoke(Object proxy, Method method, Object[] args) ,我们留意一下下Plugin的属性和构造方法,再看下invoke方法。

image.png

2.接下来看下invoke的逻辑

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
    //获取接口在映射中的value值,如果拦截的方法在@Signature中有声明,那么调用mybatis拦截器的intercept(),这里的target可能是初始的目标对象也可能是被包装的代理对象,递归返回
      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);
    }
  }

3.interceptor逻辑看具体的实现子类,mybatis-plus的MybatisPlusInterceptor下篇文章再讲了。

image.png