mybatis 插件

插件的使用

1、在配置文件配置plugins

<plugins>
    <plugin interceptor="com.test.plugin.MyBatisInterceptor"></plugin>
    ...
</plugins>

2、拦截器开发

实现Interceptor接口,在对应的拦截器类上配置注解,指定拦截方法

@Intercepts(
        @Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
)
public class MyBatisInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //TODO 拦截方法前处理
        //执行被代理方法
        Object obj = invocation.proceed();
        //TODO 拦截方法后处理
        return obj;
    }
}

Intercepts注解可以配置多个Signature注解,有以下三个配置项:

type:指定拦截类型

method:指定拦截方法

args: 拦截方法参数。

这三个参数就能完全确定一个方法。比如下面的配置。

如上面的拦截器就是要拦截Executor.query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler);方法。即该方法会被代理,走我们上面定义的拦截器的intercept方法。

mybatis官方文档也提到一般建议在下列方法进行插件拦截

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)
    这四个地方包含了sql执行的整个过程。

源码分析

配置解析

XMLConfigBuilder.pluginElement()会解析配置的plugins信息。将配置的插件信息存放到InterceptorChain类的interceptors中。

InterceptorChain 类有两个主要方法。addInterceptor解析配置时候使用。pluginAll方法在Configuration对象创建对应的sql处理类时候会调用改方法。

public class InterceptorChain {
  //保存所有的拦截器配置
  private final List<Interceptor> interceptors;
  //创建executor或handler时候调用
  public Object pluginAll(Object target) {
    //验证所有的intercepter和当前要创建的对象是否匹配
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
  //解析plugins配置时候调用
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
}

代理创建

在Configuration创建处理对象时候会调用pluginAll方法。下面四处会调用pluginAll方法。
image

再看plugin方法。interceptor的plugin在interceptor接口里有默认实现。这也是jdk8的新特性。可以在接口里指定默认实现

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

下面来看Plugin类,这个类实现了InvocationHandler接口。不用说这里实现就是动态代理了。

public class Plugin implements InvocationHandler {
   public static Object wrap(Object target, Interceptor interceptor) {
   //解析当前interceptor所有配置的Intercepts注解,拦截的所有方法
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    //如果拦截器配置的拦截方法存在与当前target,那么当前对象需要被代理,否则不需要
    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)) {
        //如果时拦截器方法,则走拦截器的intercept
        return interceptor.intercept(new Invocation(target, method, args));
      }
      //不是配置的拦截器方法,走原方法。
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
  //解析拦截器上配置的所有Signature
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    //获取Intercepts注解
    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注解
    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 {
      //解析Signature对应的method
        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;
  }
//查看当前要创建的handler或executor与拦截器相匹配的方法
  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]);
  }

}

代理链

一个方法可以配置多个拦截器。这是怎么实现的呢。

在pluginAll创建代理对象的时候我们看到是把所有的interceptor拿出来,然后循环。如果有多个这一步

target = interceptor.plugin(target); 赋值target = 新创建的target。这里就会创建多个target是个链表

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

然后在调用时候intercept方法会有一个Invocation对象。一般会调用其proceed方法,里面实现只是调用

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

这里的target已经是动态代理被代理的target了。如果target还是个代理对象则会继续下去。知道最后一个真实对象方法被调用。

总结

mybatis在执行语句时候会通过Configuration对象创建来创建对应的exexutor或handler。创建完后会调用InterceptorChain.pluginAll方法来判断该方法是否配置有对应的拦截器,判断依据是配置的@Intercepts注解里签名方法在当前target对象存在。如果有就需要为该对象创建一个代理对象(handler是Plugin对象),否则返回原对象。代理对象在执行被拦截方法首先调用Plugin.invoke方法,会触发对应拦截器的intercept方法。intercept方法就是我们定义拦截器要实现的方法。

posted @ 2023-08-14 17:03  朋羽  阅读(60)  评论(0编辑  收藏  举报