Mybatis插件开发及执行原理

mybatis源码下载

https://github.com/mybatis/mybatis-3,本文分析源码版本3.4.5

mybatis启动大致流程

在看这篇文章前,建议查看我另一篇文章,以了解框架启动的流程和框架中一些重要对象:https://blog.csdn.net/Aqu415/article/details/79049739

Mybatis中可以被插件代理的类

Mybatis中可以被插件的类型分为4种,分别是Executor、StatementHandler、ParameterHandler、ResultSetHandler。

InterceptorChain

这里说一个在插件开发过程中比较重要的类,这个类会缓存系统中所有的插件类(一个Configuration对应一个InterceptorChain);InterceptorChain中缓存的插件会在框架启动时伴随解析配置文件中plugins节点完成
InterceptorChain代码如下:

package org.apache.ibatis.plugin;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author Clinton Begin
 */
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

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

我们看看哪些地方调用了该方法:
在这里插入图片描述

调用这个方法的地方有如下(可见上面说的四大对象都是由Configuration这个类创建的):

1、org.apache.ibatis.session.Configuration#newExecutor
2、org.apache.ibatis.session.Configuration#newStatementHandler
3、org.apache.ibatis.session.Configuration#newParameterHandler
4、org.apache.ibatis.session.Configuration#newResultSetHandler

目标对象被代理的时机

我们挑第一个方法来看看,发现目标对象被代理的时机在对象创建后直接调用interceptorChain.pluginAll方法完成的。

  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);
    }
    
// @A
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

@A:调用InterceptorChain对已经创建好的Executor对象进行代理(插件化包装),调用pluginAll方法实则是调用每一个插件的plugin方法

如下是 ParameterHandler 的方法定义:

package org.apache.ibatis.executor.parameter;

import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * A parameter handler sets the parameters of the {@code PreparedStatement}
 *
 * @author Clinton Begin
 */
public interface ParameterHandler {

  Object getParameterObject();

  void setParameters(PreparedStatement ps)
      throws SQLException;
}

这个类定义的接口主要功能是设置sql执行时参数,如果需要对参数进行自定义处理(比如加密)可以自定义针对ParameterHandler 的插件

插件的编写

需要实现 org.apache.ibatis.plugin.Interceptor接口,编程模式可以参考框架提供的例子类

package org.apache.ibatis.builder;

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;

import java.util.Properties;

// @B
@Intercepts({})
public class ExamplePlugin implements Interceptor {

  private Properties properties;
  
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }

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

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

  public Properties getProperties() {
    return properties;
  }
}

@A:调用Plugin类生成一个代理类,返回这个代理对象
@B:完善需要拦截的目标类,指定方法和参数类型(作用后面分析)

工具类Plugin

框架提供一个通用生成代理的工具类 org.apache.ibatis.plugin.Plugin,其实我们可以用Plugin的api来包装目标对象也可以采用自定义的方式;采用Plugin是为了后面更方便使用@Intercepts等注解对目标类和目标方法进行过滤,而不是对所有以上四种类型的所有方法进行拦截;
因为在Plugin内部方法拦截时做了特殊过滤,使用起来更方便。

我们来看看Plugin 类

package org.apache.ibatis.plugin;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.ibatis.reflection.ExceptionUtil;

/**
 * @author Clinton Begin
 */
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);
    }
  }

  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<Class<?>, Set<Method>>();
    for (Signature sig : sigs) {
      Set<Method> methods = signatureMap.get(sig.type());
      if (methods == null) {
        methods = new HashSet<Method>();
        signatureMap.put(sig.type(), methods);
      }
      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<Class<?>>();
    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、生成代理,代理即目标对象与自定义逻辑间的枢纽拦截器(对应wrap方法)
2、自己充当这个拦截器(基于jdk代理模式)
如果分开成两个类的话,分析起来会更清晰一些

invoke

我们知道基于JDK动态代理的代理对象,会在目标方法前执行其invoke方法;我们分析一下这个方法

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
    
    // @A
      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);
    }
  }

@A:这里从缓存里看是否有配置目标类和方法,如果有的话则执行拦截器的intercept方法,也就是插件的intercept方法;
目标类和目标方法是在创建代理对象的时候,通过getSignatureMap返回;其主要是解析插件上的Intercepts注解

插件对真实对象方法拦截时机

1、从以上分析如果有多个插件,那么插件之间会形成上一个插件代理下一个插件的情况;
2、由于生成四大对象时,对目标对象进行了org.apache.ibatis.plugin.InterceptorChain#pluginAll包装,那么框架真正得到的对象就是代理对象,那么其执行方法的时候就是拦截时机

Object intercept(Invocation invocation) throws Throwable;

我们接下来分析一下执行插件的intercept方法时候,传给它的参数具体是什么;其类型是:org.apache.ibatis.plugin.Invocation

package org.apache.ibatis.plugin;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author Clinton Begin
 */
public class Invocation {

// @A
  private final Object target;
// @B
  private final Method method;
// @C
  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);
  }
}

@A:目标对象,即Executor、StatementHandler、ParameterHandler、ResultSetHandler四大类型的真实对象;如果是多层插件代理同一个类型又可能是原始对象,但也有可能是上一个代理对象
@B:方法签名
@C:参数

over~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值