Mybatis源码08 - 插件机制

插件机制

MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。

默认情况下,MyBatis允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 拦截执行器的方法

  • ParameterHandler (getParameterObject, setParameters) 拦截参数的处理

  • ResultSetHandler (handleResultSets, handleOutputParameters) 拦截结果集的处理

  • StatementHandler (prepare, parameterize, batch, update, query) 拦截Sql语法构建的处理

Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的)

由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件

一:拦截器的使用

1:拦截器的介绍和配置

首先我们看下MyBatis拦截器的接口定义:

public interface Interceptor {
    Object intercept(Invocation invocation) throws Throwable;

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

    default void setProperties(Properties properties) {
    }
}

比较简单,只有3个方法。 MyBatis默认没有一个拦截器接口的实现类,开发者们可以实现符合自己需求的拦截器。

下面的MyBatis官网的一个拦截器实例:

// 这个拦截器拦截Executor接口的update方法(其实也就是SqlSession的新增,删除,修改操作)
// 所有执行executor的update方法都会被该拦截
// 
@Intercepts({@Signature(type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
    public Object intercept(Invocation invocation) throws Throwable {
        // 直接进入代理
        return invocation.proceed();
    }
    // 插件方法
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    // 参数方法
    public void setProperties(Properties properties) {
    }
}
<plugins>
    <plugin interceptor="org.format.mybatis.cache.interceptor.ExamplePlugin"></plugin>
</plugins>

2:源码分析

从源头配置文件开始分析,XMLConfigBuilder解析MyBatis全局配置文件的pluginElement私有方法

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).newInstance();
            interceptorInstance.setProperties(properties);
            configuration.addInterceptor(interceptorInstance);
        }
    }
}

具体的解析代码其实就是通过反射实例化plugin节点中的interceptor属性表示的类。

然后调用全局配置类Configuration的addInterceptor方法

public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
}

这个interceptorChain是Configuration的内部属性,类型为InterceptorChain,也就是一个拦截器链,我们来看下它的定义:

public class InterceptorChain {
    private final List<Interceptor> interceptors = new ArrayList();

    public InterceptorChain() {
    }

    public Object pluginAll(Object target) {
        Interceptor interceptor;
        for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
            interceptor = (Interceptor)var2.next();
        }

        return target;
    }

    public void addInterceptor(Interceptor interceptor) {
        this.interceptors.add(interceptor);
    }

    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(this.interceptors);
    }
}

现在我们理解了拦截器配置的解析以及拦截器的归属,现在我们回过头看下为何拦截器会拦截这些方法:

// Executor,ParameterHandler,ResultSetHandler,StatementHandler的部分方法
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
}

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
}

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
    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, autoCommit);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

以上4个方法都是Configuration的方法。这些方法在MyBatis的一个操作(新增,删除,修改,查询)中都会被执行到,执行的先后顺序是

在这里插入图片描述

这4个方法实例化了对应的对象之后,都会调用interceptorChain的pluginAll方法,就是遍历所有的拦截器,然后调用各个拦截器的plugin方法。注意:拦截器的plugin方法的返回值会直接被赋值给原先的对象。

由于可以拦截StatementHandler,这个接口主要处理sql语法的构建,因此比如分页的功能,可以用拦截器实现,只需要在拦截器的plugin方法中处理StatementHandler接口实现类中的sql即可,可使用反射实现

MyBatis还提供了@Intercepts和 @Signature关于拦截器的注解

二:代理链的设置

Mybatis支持对Executor、StatementHandler、ParameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理。

通过查看Configuration类的源代码我们可以看到,每次都对目标对象进行代理链的生成

下面以这个executor为例,Mybatis在创建executor对象的时候会执行下面的一行代码

executor = (Executor)interceptorChain.pluginAll(executor);

InterceptorChain里保存了所有的拦截器,它在mybatis初始化的时候创建。

上面这句代码的含义是调用拦截器链里的每个拦截器依次对executor进行plugin

/** 
  * 每一个拦截器对目标类都进行一次代理 
  * @param target 
  * @return 层层代理后的对象 
  */  
public Object pluginAll(Object target) {  
    for(Interceptor interceptor : interceptors) {  
        target= interceptor.plugin(target);  
    }  
    return target;  
}

下面以一个简单的例子来看看这个plugin方法里到底发生了什么

// 注解里描述的是指定拦截方法的签名 [type,method,args] (即对哪种对象的哪种方法进行拦截),它在拦截前用于决断
@Intercepts({@Signature(type = Executor.class, method ="update", args = {MappedStatement.class, Object.class})})  
public class ExamplePlugin implements Interceptor { 
    // 实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进
    // 也就是调用下一个拦截器拦截目标方法
    @Override  
    public Object intercept(Invocation invocation) throws Throwable {  
        // 显式的推进
        return invocation.proceed();  
    }  

    // 用当前这个拦截器生成对目标target的代理
    // 实际是通过Plugin.wrap(target,this)来完成的
    // 把目标target和拦截器this传给了包装函数
    @Override  
    public Object plugin(Object target) {  
        return Plugin.wrap(target, this);  
    }  

    // 设置额外的参数,参数配置在拦截器的Properties节点里
    @Override  
    public void setProperties(Properties properties) {  
    }
}

定义自己的Interceptor最重要的是要实现plugin方法和intercept方法,在plugin方法中我们可以决定是否要进行拦截进而决定要返回一个什么样的目标对象。

而intercept方法就是要进行拦截的时候要执行的方法。

对于plugin方法而言,其实Mybatis已经为我们提供了一个实现 -> Plugin的类

里面有一个静态方法wrap(Object target,Interceptor interceptor),通过该方法可以决定要返回的对象是目标对象还是对应的代理。

这里我们先来看一下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;

//这个类是Mybatis拦截器的核心,大家可以看到该类继承了InvocationHandler
//又是JDK动态代理机制
public class Plugin implements InvocationHandler {

  //目标对象
  private Object target;
  //拦截器
  private Interceptor interceptor;
  //记录需要被拦截的类与方法
  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;
  }

  //一个静态方法,对一个目标对象进行包装,生成代理类。
  public static Object wrap(Object target, Interceptor interceptor) {
    //首先根据interceptor上面定义的注解 获取需要拦截的信息
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    //目标对象的Class
    Class<?> type = target.getClass();
    //返回需要拦截的接口信息
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    //如果长度为>0 则返回代理类 否则不做处理
    if (interfaces.length > 0) {
      // 返回代理类,JDK动态代理的实现
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

  //代理对象每次调用的方法
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //通过method参数定义的类 去signatureMap当中查询需要拦截的方法集合
      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);
    }
  }

  //根据拦截器接口(Interceptor)实现类上面的注解获取相关信息
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    //获取注解信息
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    //为空则抛出异常
    if (interceptsAnnotation == null) { // issue #251
      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<Class<?>, Set<Method>>();
    //循环注解信息
    for (Signature sig : sigs) {
      //根据Signature注解定义的type信息去signatureMap当中查询需要拦截方法的集合
      Set<Method> methods = signatureMap.get(sig.type());
      //第一次肯定为null 就创建一个并放入signatureMap
      if (methods == null) {
        methods = new HashSet<Method>();
        signatureMap.put(sig.type(), methods);
      }
      try {
        //找到sig.type当中定义的方法 并加入到集合
        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;
  }

  //根据对象类型与signatureMap获取接口信息
  private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<Class<?>>();
    //循环type类型的接口信息 如果该类型存在与signatureMap当中则加入到set当中去
    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()]);
  }
}

三:Plugin.wrap方法

从前面可以看出,每个拦截器的plugin方法是通过调用Plugin.wrap方法来实现的。代码如下:

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) {  
        // 生成代理对象, Plugin对象为该代理对象的InvocationHandler  (InvocationHandler属于java代理的一个重要概念,不熟悉的请参考相关概念)  
        return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target,interceptor,signatureMap));  
    }  
    return target;  
}

这个Plugin类有三个属性:

private Object target;// 被代理的目标类

private Interceptor interceptor;// 对应的拦截器

private Map<Class<?>, Set<Method>> signatureMap;// 拦截器拦截的方法缓存

再来看看Plugin中的getSignatureMap()方法

  1. 首先会拿到拦截器这个类的 @Interceptors注解,然后拿到这个注解的属性 @Signature注解集合
  2. 然后遍历这个集合,遍历的时候拿出 @Signature注解的type属性(Class类型)
  3. 然后根据这个type得到带有method属性和args属性的Method

由于 @Interceptors注解的 @Signature属性是一个属性

所以最终会返回一个以type为key,value为Set<Method>的Map

private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    if (interceptsAnnotation == null) { // issue #251
      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;
}
// 比如这个 @Interceptors注解会返回一个key为Executor,value为集合
// 这个集合只有一个元素,也就是Method实例,这个Method实例就是Executor接口的update方法,且这个方法带有MappedStatement和Object类型的参数
// 这个Method实例是根据 @Signature的method和args属性得到的。
// 如果args参数跟type类型的method方法对应不上,那么将会抛出异常。
@Intercepts({
    @Signature(
        type= Executor.class, 
        method = "update", 
        args = {MappedStatement.class,Object.class}
    )
})

再来看看Plugin中的getAllInterface()方法

根据目标实例target和它的父类们,返回signatureMap中含有target实现的接口数组。

所以Plugin这个类的作用就是根据 @Interceptors 注解,得到这个注解的属性 @Signature 数组

然后根据每个 @Signature注解的type,method,args属性使用反射找到对应的Method。

最终根据调用的target对象实现的接口决定是否返回一个代理对象替代原先的target对象。

/**
 * 获取指定类及其超类实现的所有接口,并根据给定的签名映射过滤
 * 该方法遍历给定类的超类链,收集所有被指定签名映射所包含的接口
 * 这对于从类继承结构中收集特定方法签名相关的接口非常有用
 * 
 * @param type 类型,用于获取其接口和超类信息
 * @param signatureMap 包含类及其方法签名的映射,用于过滤接口
 * @return 过滤后的接口数组,这些接口出现在给定类型的接口或其超类实现的接口中
 */
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    // 用于存储所有符合条件的接口
    HashSet<Class<?>> interfaces = new HashSet<>();
    // 遍历类的超类链,从当前类开始,直到到达Object类(超类为null)
    while (type != null) {
        // 获取当前类实现的所有接口
        Class<?>[] var3 = type.getInterfaces();
        // 遍历当前类的所有接口
        for (Class<?> c : var3) {
            // 如果接口的方法签名存在于签名映射中,则添加到结果集中
            if (signatureMap.containsKey(c)) {
                interfaces.add(c);
            }
        }
        // 移动到下一个超类
        type = type.getSuperclass();
    }
    // 返回过滤后的接口数组
    return interfaces.toArray(new Class<?>[0]);
}

四:代理链上的拦截

代理链生成后,对原始目标的方法调用都转移到代理者的invoke方法上来了

当Configuration调用newExecutor方法的时候

由于Executor接口的update(MappedStatement ms, Object parameter)方法被拦截器被截获。

因此最终返回的是一个代理类Plugin,而不是Executor。

这样调用方法的时候,如果是个代理类,那么会执行:

/**
 * 该方法用于拦截代理对象上的方法调用
 * 它检查当前被调用的方法是否在预定义的签名集合中,如果是,则使用拦截器进行处理,
 * 否则直接调用目标对象的方法
 * 
 * @param proxy 代理对象,通过此对象进行方法调用
 * @param method 正在被调用的方法
 * @param args 调用方法时传递的参数
 * @return 方法调用的结果
 * @throws Throwable 如果方法调用过程中抛出了异常
 */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 获取当前被调用类的所有方法的集合
        Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
        // 判断当前方法是否在集合中,如果在,则使用拦截器处理,否则直接调用目标对象的方法
        return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
    } catch (Exception var5) {
        // 将捕获到的异常转换为未包装的Throwable抛出
        throw ExceptionUtil.unwrapThrowable(var5);
    }
}

如果找到对应的方法被代理之后,那么会执行Interceptor接口的interceptor方法。

在invoke里,如果方法签名和拦截中的签名一致,就调用拦截器的拦截方法

可以看到,看到传递给拦截器的是一个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 proceed() throws InvocationTargetException, IllegalAccessException {  
        return method.invoke(target, args);  
    }  
}

可以看到,Invocation类保存了代理对象的目标类,执行的目标类方法以及传递给它的参数。

在每个拦截器的intercept方法内,最后一个语句一定是return invocation.proceed()(不这么做的话拦截器链就断了,你的mybatis基本上就不能正常工作了)。

invocation.proceed()只是简单的调用了下target的对应方法,如果target还是个代理,就又回到了上面的Plugin.invoke()方法了。这样就形成了拦截器的调用链推进

public Object intercept(Invocation invocation) throws Throwable {  
    //完成代理类本身的逻辑  
    ...
    //通过invocation.proceed()方法完成调用链的推进
    return invocation.proceed();
}

五:总结

MyBatis拦截器接口提供的3个方法中:

  • plugin方法用于某些处理器(Handler)的构建过程。
  • interceptor方法用于处理代理类的执行。
  • setProperties方法用于拦截器属性的设置。

其实MyBatis官网提供的使用 @Interceptors和 @Signature 注解以及Plugin类这样处理拦截器的方法,我们不一定要直接这样使用。

我们也可以抛弃这3个类,直接在plugin方法内部根据target实例的类型做相应的操作。

总体来说MyBatis拦截器还是很简单的,拦截器本身不需要太多的知识点,但是学习拦截器需要对MyBatis中的各个接口很熟悉,因为拦截器涉及到了各个接口的知识点

我们假设在MyBatis配置了一个插件,在运行时会发生什么?

  • 所有可能被拦截的处理类都会生成一个代理
  • 处理类代理在执行对应方法时,判断要不要执行插件中的拦截方法
  • 执行插接中的拦截方法后,推进目标的执行
  • 如果有N个插件,就有N个代理,每个代理都要执行上面的逻辑。这里面的层层代理要多次生成动态代理,是比较影响性能的。虽然能指定插件拦截的位置,但这个是在执行方法时动态判断,初始化的时候就是简单的把插件包装到了所有可以拦截的地方。

因此,在编写插件时需注意以下几个原则:

  • 不编写不必要的插件;
  • 实现plugin方法时判断一下目标类型,是本插件要拦截的对象才执行Plugin.wrap()方法,否者直接返回目标本省,这样可以减少目标被代理的次数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值