PageHelper 插件

91 阅读3分钟

XMLConfigBuilder#pluginElement()方法

XMLConfigBuilder#pluginElement()

  if (parent != null) {
    // 遍历子节点
    for (XNode child : parent.getChildren()) {
      // 获取interceptor标签下拦截器的全限定类名
      String interceptor = child.getStringAttribute("interceptor");
      // 获取子节点对应的属性
      Properties properties = child.getChildrenAsProperties();
      // 通过上面获取得拦截器的全限定类名构建一个拦截器实例
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
      // 将子节点对应的属性设置到拦截器
      interceptorInstance.setProperties(properties);
      // 将该拦截器实例设置到Configuration对象
      configuration.addInterceptor(interceptorInstance);
    }
  }
}

 Interceptor接口

 
  Object intercept(Invocation invocation) throws Throwable;
 
  Object plugin(Object target);
 
  void setProperties(Properties properties);
 
}

可以看出这是一个接口,其中定义了 intercept()、plugin() 以及 setProperties() 三个方法:

  • intercept() 方法:覆盖所拦截对象原有的方法,也是插件的核心方法。其入参是 invocation,可以利用反射调原对象的方法;
  • plugin() 方法:入参 target 是被拦截的对象,该方法的作用是生成一个被拦截对象的代理实例;
  • setProperties() 方法:方便把 MyBatis 配置文件中 plugin 标签下的内容解析后设置到 Configuration 对象。
  • 对于一个插件来说,必须要先实现 Intercept 接口中的三个方法才能在 MyBatis 框架中进行配置并使用。

从PageHelper源码来看插件的实现流程 本节中会从 PageHelper 的源代码来分析 MyBatis 插件的实现流程,找到关键类 PageInterceptor 的源码如下:

@SuppressWarnings({"rawtypes", "unchecked"})
// 拦截器的注解
@Intercepts(
    {
                // 注册拦截器签名:指定需要被拦截的类型(type)、方法(method)和参数(args)需要被拦截
                // 只包含4个参数
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                // 只包含6个参数
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    }
)
public class PageInterceptor implements Interceptor {
// 缓存count查询的ms
  protected Cache<String, MappedStatement> msCountMap = null;
  private Dialect dialect;
  private String default_dialect_class = "com.github.pagehelper.PageHelper";
  private Field additionalParametersField;
  private String countSuffix = "_COUNT";
 
  @Override
  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];
      }
      List resultList;
      // 调用方法判断是否需要进行分页,如果不需要,直接返回结果
      if (!dialect.skip(ms, parameter, rowBounds)) {
        // 反射获取动态参数
        String msId = ms.getId();
        Configuration configuration = ms.getConfiguration();
        Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
        // 判断是否需要进行count查询
        if (dialect.beforeCount(ms, parameter, rowBounds)) {
          String countMsId = msId + countSuffix;
          Long count;
          // 先判断是否存在手写的count查询
          MappedStatement countMs = getExistedMappedStatement(configuration, countMsId);
          if(countMs != null){
            count = executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
          } else {
            countMs = msCountMap.get(countMsId);
            // 自动创建
            if (countMs == null) {
              // 根据当前的ms创建一个返回值为Long类型的ms
              countMs = MSUtils.newCountMappedStatement(ms, countMsId);
              msCountMap.put(countMsId, countMs);
            }
            count = executeAutoCount(executor, countMs, parameter, boundSql, rowBounds, resultHandler);
          }
          // 处理查询总数
          // 返回true时继续分页查询,false时直接返回
          if (!dialect.afterCount(count, parameter, rowBounds)) {
            // 当查询总数为0时,直接返回空的结果
            return dialect.afterPage(new ArrayList(), parameter, rowBounds);
          }
        }
        // 判断是否需要进行分页查询
        if (dialect.beforePage(ms, parameter, rowBounds)) {
          // 生成分页的缓存key
          CacheKey pageKey = cacheKey;
          // 处理参数对象
          parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
          // 调用方言获取分页sql
          String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
          BoundSql pageBoundSql = new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter);
          // 设置动态参数
          for (String key : additionalParameters.keySet()) {
            pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
          }
          // 执行分页查询
          resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
        } else {
          // 不执行分页的情况下,也不执行内存分页
          resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
        }
      } else {
        // rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
        resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
      }
      return dialect.afterPage(resultList, parameter, rowBounds);
    } finally {
      dialect.afterAll();
    }
  }
 
  // 省略……
     
  @Override
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
 
  @Override
  public void setProperties(Properties properties) {
    // 缓存count ms
    msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
    String dialectClass = properties.getProperty("dialect");
    if (StringUtil.isEmpty(dialectClass)) {
      dialectClass = default_dialect_class;
    }
    try {
      Class<?> aClass = Class.forName(dialectClass);
      dialect = (Dialect) aClass.newInstance();
    } catch (Exception e) {
      throw new PageException(e);
    }
    dialect.setProperties(properties);
 
    String countSuffix = properties.getProperty("countSuffix");
    if (StringUtil.isNotEmpty(countSuffix)) {
      this.countSuffix = countSuffix;
    }
 
    try {
      // 反射获取BoundSql中的additionalParameters属性
      additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
      additionalParametersField.setAccessible(true);
    } catch (NoSuchFieldException e) {
      throw new PageException(e);
    }
  }
}

简单看完 PageHelper 插件的实现代码之后,总结一下 MyBatis 框架中自定义插件的步骤:

  • 确定要被拦截的签名:根据 @Intercepts 以及 @Signature 注解指定需要拦截的参数;
  • 实现 Intercept 接口的 intercept()、plugin() 以及 setProperties() 方法。