Mybatis源码阅读之--插件实现原理分析

前言:
mybatis为了方便用户在使用过程中,对某些特定的执行点进行添加自己的处理,提供了插件功能,这些插件可以在一个语句执行过程中的特定点进行拦截。

使用方法:

  1. 新建一个类实现Interceptor接口
  2. 在类上使用@Intercepts和@Signature注解指定在哪些地方进行拦截
  3. 在mybatis配置文件中使用 添加插件

// 使用注解指定在哪些地方进行拦截
// 可以拦截多个接口
@Intercepts({
  @Signature(
    type=Execution.class, 
    method="udpate", 
    args={MappedStatement.class, Object.class}
)})
public class ExamplePlugin implements Inteceptor {
  // 可以设置一些属性
  private Properties properties = new Properties();

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    // implement pre-processing if needed
    Object returnObject = invocation.proceed();
    // implement post-processing if needed
    return returnObject;
  }

  @Override
  public void setProperties(Properties properties) {
    this.properties = properties;
  }

}

以上代码意思是在Execution执行的update(MappedStatement, Object)这个方法出进行拦截
可以拦截的类:

  1. Executor
  2. StatementHandler
  3. ResultSetHandler
  4. ParameterHandler
    以上接口的所有公共方法都可以进行拦截

plugin的初始化动作通过XMLConfigBuilder进行plugin对象的创建与注册

  // XMLConfigBuilder类
  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        interceptorInstance.setProperties(properties);
        // 在这里添加了各种plugin,plugin的用法参见https://mybatis.org/mybatis-3/configuration.html#plugins
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

主要过程:

  1. 获取plugin的interceptor属性
  2. 获取定义的各个properties
  3. 解析类并创建对象(使用无参构造方法进行构造)
  4. 向interceptor对象中设置属性
  5. 将interceptor添加至configuration对象中--此步骤主要时间interceptor放入interceptorChain中
  // Configuration类
  public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }

InterceptorChain类的代码如下:

public class InterceptorChain {

  // 存放所有的插件,任何插件都是要实现Interceptor接口
  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来存储所有的Interceptor
以上就完成了所有Interceptor的注册功能,那么Mybatis是如何将这些Interceptor植入到相应地方呢?上文中说了四个类可以进行拦截,这四个类的对象都是在Configuration中进行创建的。

  1. Executor
// Configuration类
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }

    // 将所有的interceptor包含在executor中
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

创建Executor的最后一步就是将所有的Interceptor进行植入,这里是调用InterceptorChain进行植入的,上文中已经列出了InterceptorChain的代码,这里再次列出其pluginAll方法,以方便查看

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

此方法的入口是目标对象,然后调用每个interceptor的plugin进行植入,Interceptor接口提供了plugin方法的默认实现

// Interceptor接口中
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

这里是调用Plugin.wrap方法进行植入

public class Plugin implements InvocationHandler {

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

  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
    }
    Signature[] sigs = interceptsAnnotation.value();
    // 一个class对应多个method
    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. 解析@Intercepts注解,一个Intercepts可以有多个Signature,而一个Signature指定了一个类中的一个方法,因此Intercepts可以指定一个类中的多个方法和多个类中的多个方法
    并返回map,map表示此拦截器拦截了哪些类的哪些方法
  2. 根据targe对象的类型,多的其实现的所有接口,对每个接口进行判断,看其是否在上一步返回的map中
  3. 如果此interceptor拦截的接口中拥有目标对象中实现的接口,那么就为目标对象新建一个代理对象(使用JDK动态代理的方式)并返回,否则不创建代理直接返回目标对象

我们看到为目标对象创建代理对象时,InvocationHandler使用的是new Proxy(),这里的Proxy实现了InvocationHandler接口,
而interfaces是target实现了,并且在Intercepts中配置了的所有接口,也就是说如果target实现了接口A,但是Intercepts中没有A的Signature,那么队里对象也不会实现接口A。(思考如果遇到这种情况,新建的代理对象可以代表target吗?)

进一步去看Proxy的invoke方法(调用targe对象的任何方法都会先执行invoke):

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

这里我们看到,如果当前调用的方法在Intercepts中指定了,那么就调用interceptor.intercept方法,也就是我们自己实现的方法,此方法接受一个封装号的Invocation对象,否则直接执行目标对象的方法。

Invocation是Mybatis封装的一个方法调用,我们可以通过此对象完成原有方法的执行,以及获取一些方法调用的信息(调用的是哪个方法,以及方法的参数等)

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插件功能允许我们在Mybatis执行过程中某些特定的地方植入一些自己的处理,
例如我们想在Execution.update方法中加上一些日志的功能,那么就可以用插件功能实现,其使用了Java动态代理实现。

猜你喜欢

转载自www.cnblogs.com/autumnlight/p/12659687.html