likes
comments
collection
share

详解Mybatis插件模块及分页插件PageHelper

作者站长头像
站长
· 阅读数 37

Mybatis 插件功能,是通过拦截器(Interceptor)实现的,应用了简单工厂模式、责任链模式和动态代理模式。分页是插件模块常见的应用场景,PageHelper是最常用的分页插件之一。

Mybatis 简单介绍

本部分简单介绍一下Mybatis的初始化流程,适合只有Mybatis使用经验而没有源码阅读经验的同学。 在Mybatis的初始化过程中,主要通过读取配置文件,创建相应的配置对象,最终完成框架各个模块的初始化。读取的配置信息,会被封装到Configuration对象的对应属性中。

XMLConfigBuilder 主要负责解析配置文件

private void parseConfiguration(XNode root) {
  try {
    // issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfsImpl(settings);
    loadCustomLogImpl(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginsElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlersElement(root.evalNode("typeHandlers"));
    mappersElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

当所有信息解析完成封装到Configuration后,由Configuration创建相应的对象,其中插件功能是通过创建Executor、ParameterHandler、ResultSetHandler、StatementHandler的代理对象实现。

Interceptor

Mybatis 允许用户自定义拦截器对SQL语句执行过程中的某一点进行拦截,允许拦截器拦截的方法包括:Executor中的方法、ParameterHandler中的方法、ResultSetHandler中的方法、StatementHandler中的方法。 Mybatis中使用拦截器都需要实现 Interceptor 接口,其定义如下:

public interface Interceptor {
  // 执行拦截逻辑的方法
  Object intercept(Invocation invocation) throws Throwable;

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

  default void setProperties(Properties properties) {
    // NOP
  }

}

用户自定义拦截器,还需要使用 @Intercepts@Signature 两个注解标识

  • @Intercepts 注解:指定一个 @Signature 注解列表
  • @Signature:每个 @Signature 注解中都标识了该插件需要拦截的方法信息,其中 @Signature 注解的 type 属性指定需要拦截的类型,method 属性指定需要拦截的方法,args 属性指定被拦截方法的参数列表。通过这3个注解,@Signature 就可以表示一个方法签名,唯一确定一个方法。

例如 PageHelper 插件中定义的 PageInterceptor 拦截器,该拦截器可以拦截 Executor 接口中的两个 query 方法。

@Intercepts(
        {
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        }
)
public class PageInterceptor implements Interceptor {
    private static final Log                            log                   = LogFactory.getLog(PageInterceptor.class);
    

}

在 Mybatis 初始化时,通过 MLConfigBuilder#pluginsElement()方法解析配置文件中的 <plugins>节点,得到相应的 Interceptor 对象,以及配置的相关属性,然后调用 Interceptor.setProperties(properties) 方法完成对 Interceptor 对象的初始化配置,最后将 Interceptor 对象添加到 Configuration#interceptorChain (InterceptorChain 类型)字段,InterceptorChain 底层使用ArrayList<Interceptor>实现。

public class InterceptorChain {

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

}

Configuration#new*()方法

以 Configuration.newExecutor() 为例进行分析:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  Executor 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);
  }
  // interceptorChain.pluginAll 方法创建的 Executor 的代理对象
  return (Executor) interceptorChain.pluginAll(executor);
}

InterceptorChain 中使用 interceptors 字段(ArrayList<Interceptor> 类型)记录了配置的拦截器。在 InterceptorChain#pluginAll 方法中,会遍历 interceptors 集合中的每个元素,调用其中的 plugin() 方法创建代理对象,代码如下:

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

用户自定义拦截器实现的 plugin() 方法,可以直接使用 Plugin 工具类实现,它实现了 InvocationHandler 接口,并提供了一个 wrap() 静态方法用于创建代理对象, 该方法的返回值即为创建的代理对象, 方法接受2个参数:

  • Object target:目标对象,例如当前是从newExecutor() 方法进来的,那么这个 target 类型就可能是如下类型的一种:BatchExecutor、ReuseExecutor、SimpleExecutor、CachingExecutor

  • Interceptor interceptor:拦截器,例如分页插件的拦截器 PageInterceptor

public static Object wrap(Object target, Interceptor interceptor) {
  // 获取用户自定义 Interceptor 中 @Signature 注解信息 
  // getSignatureMap() 方法负责处理 @Signature 注解 ,具体实现见下方
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    
  // 获取目标类型
  Class<?> type = target.getClass();

  // 获取目标类型实现的接口,例如当前是从newExecutor() 方法进来的,获取的就是 Executor
  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  if (interfaces.length > 0) {
    // 使用 JDK动态代理的方式创建代理对象(不了解JDK动态代理的,强烈安利阅读上述提到的文章)
    // new Plugin(target, interceptor, signatureMap) 这里的 Plugin 是 InvocationHandler 的实现
    return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
  }
  return target;
}

getSignatureMap() 方法实现:

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<>();
  for (Signature sig : sigs) {
    Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, 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;
}

Plugin

JDK 动态代理的实现,通过 InvocationHandler 实现增强逻辑,Proxy创建代理对象。Plugin 实现了 InvocationHandler。

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

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 获取当前方法所在类或接口中,可被当前 Interceptor 拦截的方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      
      // 如果当前调用的方法需要被拦截,则调用 interceptor.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);
    }
  }

Plugin 中定义了3个字段:

  • target:目标对象
  • interceptor:Interceptor 对象
  • signatureMap:记录了 @Signature 注解中的信息

Plugin.invoke() 方法在代理对象被调用的时候执行,当需要执行增强逻辑的时候,会调用 nterceptor#intercept 方法。以下是 PageInterceptor 中 intercept 的实现,源码中写了详细的注释,就不一一展开了。

public Object intercept(Invocation invocation) throws Throwable {
    try {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];
        Executor executor = (Executor) invocation.getTarget();
        CacheKey cacheKey;
        BoundSql boundSql;
        //由于逻辑关系,只会进入一次
        if (args.length == 4) {
            //4 个参数时
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            //6 个参数时
            cacheKey = (CacheKey) args[4];
            boundSql = (BoundSql) args[5];
        }
        checkDialectExists();
        //对 boundSql 的拦截处理
        if (dialect instanceof BoundSqlInterceptor.Chain) {
            boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);
        }
        List resultList;
        //调用方法判断是否需要进行分页,如果不需要,直接返回结果
        if (!dialect.skip(ms, parameter, rowBounds)) {
            //开启debug时,输出触发当前分页执行时的PageHelper调用堆栈
            // 如果和当前调用堆栈不一致,说明在启用分页后没有消费,当前线程再次执行时消费,调用堆栈显示的方法使用不安全
            debugStackTraceLog();
            Future<Long> countFuture = null;
            //判断是否需要进行 count 查询
            if (dialect.beforeCount(ms, parameter, rowBounds)) {
                if (dialect.isAsyncCount()) {
                    countFuture = asyncCount(ms, boundSql, parameter, rowBounds);
                } else {
                    //查询总数
                    Long count = count(executor, ms, parameter, rowBounds, null, boundSql);
                    //处理查询总数,返回 true 时继续分页查询,false 时直接返回
                    if (!dialect.afterCount(count, parameter, rowBounds)) {
                        //当查询总数为 0 时,直接返回空的结果
                        return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                    }
                }
            }
            resultList = ExecutorUtil.pageQuery(dialect, executor,
                    ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
            if (countFuture != null) {
                Long count = countFuture.get();
                dialect.afterCount(count, parameter, rowBounds);
            }
        } else {
            //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
            resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        }
        return dialect.afterPage(resultList, parameter, rowBounds);
    } finally {
        if (dialect != null) {
            dialect.afterAll();
        }
    }
}
转载自:https://juejin.cn/post/7387226606414069769
评论
请登录