mybatis之插件分析

本文详细介绍了如何利用Mybatis插件功能,实现在调用Map的get方法时统一返回预设值。通过自定义Interceptor接口和@Intercepts注解,展示了如何创建代理对象并拦截特定方法。重点讲解了Plugin、InterceptorChain和配置插件的过程。

写在前面

想要系统学习的,可以参考这篇文章,重要!!!
一个框架之所以优秀,除了本身提供的强大的既有功能之外,还允许根据自己特定的需求和业务场景来进行扩展,mybatis同样提供了这样的功能,是通过插件功能来实现的,对应的是org.apache.ibatis.plugin包,如下图:
在这里插入图片描述
其中org.apache.ibatis.plugin.Interceptor是插件对应的接口,源码如下:

org.apache.ibatis.plugin.Interceptor
public interface Interceptor {
  // 执行拦截的方法,我们可以在这里添加扩展逻辑
  Object intercept(Invocation invocation) throws Throwable;
  // 应用目标对象,返回代理对象
  Object plugin(Object target);
  // 设置在<plugin>标签中配置的<property>子标签们,如下可能配置
  /*
  <plugins>
      <plugin interceptor="yudaosourcecode.mybatis.interceptor.MyInterceptor">
          <property name="xxx" value="yyyy"/>
          <property name="aaaa" value="bbbb"/>
      </plugin>
   </plugins>
  */
  // 即自定义的属性信息
  void setProperties(Properties properties);

}

1:一个小例子

我们先来实现这样的效果,通过这里的API实现:生成java.util.Map的动态代理对象,当调用其get方法时不管目标key的实际值时什么,都返回“dongshidaddy”,
首先定义实现类:

@Intercepts({
        @Signature(type = java.util.Map.class, method = "get", args = Object.class)
})
public class MyMapProxy implements Interceptor {
    // org.apache.ibatis.plugin.Invocation是一个封装目标类,目标方法,目标参数的对象,如下:
    /*
	public class Invocation {

	  private Object target;
	  private Method method;
	  private 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);
	  }
	
	}
	*/
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 直接返回dongshidaddy,不管目标真实值时什么
        return "dongshidaddy";
    }

    @Override
    public Object plugin(Object target) {
        // 通过org.apache.ibatis.plugin.Plugin的API生成目标对象的代理对象
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

测试代码:

public static void main(String[] args) throws Exception {
    // MyMapProxy
    Map<String, String> myMap = new HashMap<>();
    myMap.put("name", "jack");
    myMap.put("age", "90");
    Map myMapAfterProxy = (Map)new MyMapProxy().plugin(myMap);
    System.out.println("通过原始对象获取实际name和age:");
    System.out.println("name: " + myMap.get("name"));
    System.out.println("age: " + myMap.get("age"));
    System.out.println("通过代理对象获取实际name和age:");
    System.out.println("name: " + myMapAfterProxy.get("name"));
    System.out.println("age: " + myMapAfterProxy.get("age"));
}

运行如下:

通过原始对象获取实际name和age:
name: jack
age: 90
通过代理对象获取实际name和age:
name: dongshidaddy
age: dongshidaddy

MyMapProxy类实现了org.apache.ibatis.plugin.Intercetor接口,这是mybaits为了让我们自定义插件而定义的规范接口接口先行吗,然后通过注解@org.apache,ibatis.plugin.Intercepts定义了要拦截的类,方法,参数等信息,如我们的定义@Signature(type = java.util.Map.class, method = "get", args = Object.class),含义如下:

@Signature:
	定义方法的签名,即唯一确定要拦截的方法。
type = java.util.Map.class:
	要拦截的class类型是java.util.Map.class。
method = "get":
	要拦截的方法名称是get。
args=Object.class:
	方法的参数是Object.class。

这样子当我们调用Plugin.wrap(target, this)方法的时候就会通过这里的配置生成对应的代理对象了,而生成的代理对象拦截目标方法的方法就是org.apache.ibatis.plugin.Interceptor#intercept,这样就达到了拦截方法的目的了,就可以在拦截的逻辑里增加自定义内容了。
关于wrap方法生成代理对象,具体参考2.1:wrap生成代理对象

2:Plugin

2.1:wrap生成代理对象

源码如下:

org.apache.ibatis.plugin.Plugin#wrap
public static Object wrap(Object target, Interceptor interceptor) {
  // 获取@org.apache.ibatis.plugin.Intercepts注解配置要拦截的方法信息
  // 2021-08-31 11:21:40
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  // 获取需要代理类的class类型
  Class<?> type = target.getClass();
  // 根据需要代理类实现的所有接口,用于通过jdk动态代理API生成代理类
  // 2021-08-31 13:34:13
  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  // 如果是实现了接口则生成代理类
  if (interfaces.length > 0) {
    // 通过java.lang.reflect.Proxy的newProxyInstance静态方法生成代理类
    // 2021-08-31 14:37:43
    return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
  }
  return target;
}

2021-08-31 11:21:40处是获取注解配置的要拦截方法信息,如下配置:

@Intercepts({
        @Signature(type = java.util.Map.class, method = "get", args = Object.class),
        @Signature(type = java.util.Map.class, method = "put", args = { Object.class, Object.class }),
        @Signature(type = java.util.List.class, method = "add", args = { Object.class }),
        @Signature(type = java.util.List.class, method = "contains", args = { Object.class })
})

则结果如下图:
在这里插入图片描述
其中key是@org.apache,ibatis.plugin.Signature注解配置的type属性值,值是method和args组合对应的方法Method对象信息,具体参考2.1.1:解析拦截方法信息2021-08-31 13:34:13处是获取需要代理的类所有父接口,具体参考2.1.2:获取父接口2021-08-31 14:37:43处是通过反射包API生成代理类,其中的org.apache.ibatis.plugin.Plugin的构造函数源码如下:

org.apache.ibatis.plugin.Plugin
// 实现了java.lang.reflect.InvocationHandler接口
public class Plugin implements InvocationHandler {
  // 被代理的目标类
  private Object target;
  // 实现了org.apache.ibatis.plugin.Interceptor接口的类,即我们自定义的mybaits的插件类
  private Interceptor interceptor;
  // 在自定义插件类上的@org.apache.ibatis.plugin.Intercepts注解配置的方法信息
  private 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;
  }
  ...snip...
}

传到jdk动态代理的InvocationHandler的实现是org.apache.ibatis.plugin.Plugin因此会调用invoke方法进行方法调用,具体参考2.1.3:invoke

2.1.1:解析拦截方法信息

源码如下:

org.apache.ibatis.plugin.Plugin#getSignatureMap
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
 // 从class信息中获取@org.apache.ibatis.plugin.Intercepts注解信息
 Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
  // 如果是没有配置@org.apache.ibatis.plugin.Intercepts则直接异常
  // 因为需要依赖于该注解信息生成代理类,所以必须有
  if (interceptsAnnotation == null) {
    throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
  }
  // 调用value方法获取@org.apache.ibatis.plugin.Signature签名注解数组信息
  // 用于定义要拦截的方法的签名信息
  Signature[] sigs = interceptsAnnotation.value();
  // 定义用于存放结果的map
  // key->类的class,value->类下要拦截的方法的集合
  Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
  // 循环处理
  for (Signature sig : sigs) {
    // 根据当前类class获取方法数组,可能存在也可能不存在
    Set<Method> methods = signatureMap.get(sig.type());
    // 不存在的话,则初始化一个
    if (methods == null) {
      methods = new HashSet<Method>();
      signatureMap.put(sig.type(), methods);
    }
    try {
      // 根据方法的名称和参数信息,获取方法对应的java.lang.reflect.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;
}
2.1.2:获取父接口

源码如下:

org.apache.ibatis.plugin.Plugin#getAllInterfaces
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
  // 结果集合
  Set<Class<?>> interfaces = new HashSet<Class<?>>();
  // 循环type
  while (type != null) {
    // 获取所有的父接口,因为java可以实现多个接口,所以这里是一批
    for (Class<?> c : type.getInterfaces()) {
      // 必须在@org.apache.ibatis.plugin.Intercepts注解中配置的类
      if (signatureMap.containsKey(c)) {
        interfaces.add(c);
      }
    }
    // 因为java是单继承,所以这里只可能有一个父类
    type = type.getSuperclass();
  }
  // 转Class数组返回
  return interfaces.toArray(new Class<?>[interfaces.size()]);
}

截止到这里,mybaits中是如何加载这些自定义插件还没有分析到,其中定义插件和注册插件的方式可以参考这里
定义完插件之后,需要在全局配置文件中通过plugin标签进行配置,可能如下:

<plugins>
    <plugin interceptor="yudaosourcecode.mybatis.interceptor.MyInterceptor">
        <property name="xxx" value="yyyy"/>
        <property name="aaaa" value="bbbb"/>
    </plugin>
</plugins>

在mybatis启动的过程中,首先是加载全局配置文件的这些信息,具体参考3.1:读取插件信息

2.1.3:invoke

源码如下:

org.apache.ibatis.plugin.Plugin#invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    // 获取声明需要拦截的方法
    Set<Method> methods = signatureMap.get(method.getDeclaringClass());
    // 如果是有声明拦截的方法,并且当前方法包含在要拦截的方法集合中,则调用intercept方法进行拦截
    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:mybatis插件加载过程

3.1:读取插件信息

会执行到方法org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration,源码如下:

org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
  try {
    ...snip...
    // 解析<plugins/>标签
    // 2021-08-31 14:56:15
    pluginElement(root.evalNode("plugins"));
     ...snip...
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

2021-08-31 14:56:15处源码如下:

org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement
private void pluginElement(XNode parent) throws Exception {
  if (parent != null) {
    // 获取所有的<plugin/>子标签
    for (XNode child : parent.getChildren()) {
      // 获取interceptor属性值,即自定义的插件实现类全限定名称
      String interceptor = child.getStringAttribute("interceptor");
      // 获取所有的属性
      Properties properties = child.getChildrenAsProperties();
      // 强转
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
      // 调用org.apache.ibatis.plugin.Interceptor的setProperties方法设置属性信息
      interceptorInstance.setProperties(properties);
      // 添加自定义插件信息
      // 2021-08-31 14:59:02
      configuration.addInterceptor(interceptorInstance);
    }
  }
}

2021-08-31 14:59:02处是添加自定义插件到全局配置文件中,源码如下:

org.apache.ibatis.session.Configuration#addInterceptor
public void addInterceptor(Interceptor interceptor) {
  // protected final InterceptorChain interceptorChain = new InterceptorChain();
  // 2021-08-31 15:00:12
  interceptorChain.addInterceptor(interceptor);
}

2021-08-31 15:00:12处是添加自定义插件到拦截器链中,关拦截器链可以参考4.1:添加自定义插件。另外在调用器链org.apache.ibatis.plugin.InterceptorChain中有一个非常重要的方法pluginAll用来生成代理类,从而通过代理类干预mybatis正常的执行流程,如sql语句的生成,结果集的生成等。关于该方法,具体参考4.2:pluginAll

4:InterceptorChain

4.1:添加自定义插件

org.apache.ibatis.plugin.InterceptorChain#addInterceptor
public void addInterceptor(Interceptor interceptor) {
  // 添加到interceptors集合中
  // org.apache.ibatis.plugin.InterceptorChain#interceptors
  // private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
  interceptors.add(interceptor);
}

4.2:pluginAll

源码如下:

org.apache.ibatis.plugin.InterceptorChain#pluginAll
public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
    // 2021-08-31 15:54:06
    target = interceptor.plugin(target);
  }
  return target;
}

一般2021-08-31 15:54:06处我们都是通过方法Plugin.wrap来创建代理对象,该方法具体参考2.1:wrap生成代理对象,接下来我们看下都有哪些地方使用到了该方法,如下:
在这里插入图片描述
结合2.1:wrap生成代理对象处的只有当被代理类在@org.apache.ibatis.plugin.Interceptors注解中配置了的时候才会生成代理对象,否则还是返回对象本身,因此mybaits支持配置的接口如下:

org.apache.ibatis.executor.Executor->执行器,用来调用StatementHandler执行数据库操作
org.apache.ibatis.executor.statement.StatementHandler->用来调用JDBC的java.sql.Statement的处理器
org.apache.ibatis.executor.parameter.ParameterHandler->用来设置参数信息的处理器
org.apache.ibatis.executor.resultset.ResultSetHandler->用来处理结果集的处理器

因此,通过对这里的接口生成代理,我们就可以进行一些mybatis的扩展工作了,比如添加分页插件,关于分页插件的应用可以参考这篇文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值