【源码解读】Mybatis分页插件PageHelper源码解读

1,901 阅读12分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

1 简介

日常开发过程中基于mybatis的sql查询,会存在分页查询场景,可通过手动写limit实现分页,同时可依赖分页插件进行实现。pageHeler是一款基于mybatis的插件实现分页的插件,无需自己手动实现分页。

2 pageHelper的实现

2.1 pageHelper的使用

下面是基于spring+mybatis的实现

mybtais的config.xml引入plugins标签

<plugins>
        <!-- com.github.pagehelper为PageHelper类所在包名 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
                <!-- 4.0.0以后版本可以不设置该参数 -->
                <property name="helperDialect" value="mysql"/>
                <!-- 该参数默认为false -->
                <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
                <!-- 和startPage中的pageNum效果一样-->
                <property name="offsetAsPageNum" value="true"/>
                <!-- 该参数默认为false -->
                <!-- 设置为true时,使用RowBounds分页会进行count查询 -->
                <property name="rowBoundsWithCount" value="false"/>
                <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
                <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
                <property name="pageSizeZero" value="true"/>
                <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
                <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
                <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
                <property name="reasonable" value="true"/>
                <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
                <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
                <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值 -->
                <!-- 不理解该含义的前提下,不要随便复制该配置 -->
                <property name="params" value="pageNum=start;pageSize=limit;"/>
                <!-- 支持通过Mapper接口参数来传递分页参数 -->
                <property name="supportMethodsArguments" value="true"/>
                <!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
                <property name="returnPageInfo" value="check"/>
        </plugin>
    </plugins>

代码引用实现(官网提供)

//第一种,RowBounds方式的调用
List<Country> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10));

//第二种,Mapper接口方式的调用,推荐这种使用方式。
PageHelper.startPage(1, 10);
List<Country> list = countryMapper.selectIf(1);

//第三种,Mapper接口方式的调用,推荐这种使用方式。
PageHelper.offsetPage(1, 10);
List<Country> list = countryMapper.selectIf(1);

//第四种,参数方法调用
//存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
public interface CountryMapper {
    List<Country> selectByPageNumSize(
            @Param("user") User user,
            @Param("pageNum") int pageNum,
            @Param("pageSize") int pageSize);
}
//配置supportMethodsArguments=true
//在代码中直接调用:
List<Country> list = countryMapper.selectByPageNumSize(user, 1, 10);

//第五种,参数对象
//如果 pageNum 和 pageSize 存在于 User 对象中,只要参数有值,也会被分页
//有如下 User 对象
public class User {
    //其他fields
    //下面两个参数名和 params 配置的名字一致
    private Integer pageNum;
    private Integer pageSize;
}
//存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
public interface CountryMapper {
    List<Country> selectByPageNumSize(User user);
}
//当 user 中的 pageNum!= null && pageSize!= null 时,会自动分页
List<Country> list = countryMapper.selectByPageNumSize(user);

//第六种,ISelect 接口方式
//jdk6,7用法,创建接口
Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
    @Override
    public void doSelect() {
        countryMapper.selectGroupBy();
    }
});
//jdk8 lambda用法
Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(()-> countryMapper.selectGroupBy());

//也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
    @Override
    public void doSelect() {
        countryMapper.selectGroupBy();
    }
});
//对应的lambda用法
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> countryMapper.selectGroupBy());

//count查询,返回一个查询语句的count数
long total = PageHelper.count(new ISelect() {
    @Override
    public void doSelect() {
        countryMapper.selectLike(country);
    }
});
//lambda
total = PageHelper.count(()->countryMapper.selectLike(country));

2.2 实现分析

下面使用Iselect实现的方式进行

首先分析PageHelper#startPage方法,该方法有多重重载,下面是最终实现

   /**
     * 开始分页
     *
     * @param pageNum      页码
     * @param pageSize     每页显示数量
     * @param count        是否进行count查询
     * @param reasonable   分页合理化,null时用默认配置
     * @param pageSizeZero true且pageSize=0时返回全部结果,false时分页,null时用默认配置
     */
    public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
       	//构造返回对象,最终调用iselect对象后返回page对象,该对象继承ArrayList
        Page<E> page = new Page<E>(pageNum, pageSize, count);
        page.setReasonable(reasonable);
        page.setPageSizeZero(pageSizeZero);
        //当已经执行过orderBy的时候
        Page<E> oldPage = getLocalPage();
        if (oldPage != null && oldPage.isOrderByOnly()) {
            page.setOrderBy(oldPage.getOrderBy());
        }
        //将page对象设置到thradLocal中,后续会使用
        setLocalPage(page);
        return page;
    }

再iselect得实现其实是执行mapper得方法,就是正常执行mybatis得流程,再mybatis流程中会被pageHelper实现得拦截器拦截住,所以直接看PageInterceptor

/**
 *实现mybatis的Interceptor接口
 *Intercepts注解释义
 *  Signature type 需要拦截的类
 *			  method 拦截的方法
 *            args 调用query方法的参数类型
 *  该注解定义是为了再执行各种sql的时候,让mybatis知道需要对哪些方法在什么时候进行拦截
 *  Signature注解可以确定描述为一个java接口,类型,方法名,参数
 */
@SuppressWarnings({"rawtypes", "unchecked"})
@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 volatile Dialect dialect;
    //进行count的前缀
    private String countSuffix = "_COUNT";
    //缓存
    protected Cache<String, MappedStatement> msCountMap = null;
    //全路径类名,是为了初始化使用
    private String default_dialect_class = "com.github.pagehelper.PageHelper";
    
    /**
     * @Description: mybatis的拦截器执行方法
     * @param Invocation 包含三个参数 taget代理的真实对象 method 反射方法  args方法参数
     * @return  
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            //获取调用参数类型
            Object[] args = invocation.getArgs();
            //mybatis的映射信息封装
            MappedStatement ms = (MappedStatement) args[0];
            //获取真正的参数
            Object parameter = args[1];
            //获取分页信息
            RowBounds rowBounds = (RowBounds) args[2];
            //获取结果集处理器
            ResultHandler resultHandler = (ResultHandler) args[3];
            //获取sql执行器
            Executor executor = (Executor) invocation.getTarget();
            CacheKey cacheKey;
            BoundSql boundSql;
            //由于逻辑关系,只会进入一次
            if (args.length == 4) {
                //4 个参数时
                //获取boundSql,执行语句信息
                boundSql = ms.getBoundSql(parameter);
                //创建缓存
                cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
            } else {
                //6 个参数时
                cacheKey = (CacheKey) args[4];
                boundSql = (BoundSql) args[5];
            }
            //判断方言是否存在
            checkDialectExists();

            List resultList;
            //调用方法判断是否需要进行分页,如果不需要,直接返回结果
            if (!dialect.skip(ms, parameter, rowBounds)) {
                //判断是否需要进行 count 查询
                if (dialect.beforeCount(ms, parameter, rowBounds)) {
                    //查询总数,主要得重新构造count语句
                    Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                    //处理查询总数,返回 true 时继续分页查询,false 时直接返回
                    //这边的判断主要是用来判断传入的分页参数是否超过整个count的值
                    if (!dialect.afterCount(count, parameter, rowBounds)) {
                        //当查询总数为 0 时,直接返回空的结果
                        return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                    }
                }
                //执行分页查询,
                //这边语句的修改则是来处理一个点,就是我们的执行语句是非分页的,需要通过以下的操作来处理
                //分页参数,就是重盖mMappedStatement里存储的sql语句和传入分页参数
                //语句修改则是需要根据不同的数据库方言进行不同的处理
                resultList = ExecutorUtil.pageQuery(dialect, executor,
                        ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
            } else {
                //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
                resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
            }
            //该地方获取了结果集,将结果封装到page对象中
            return dialect.afterPage(resultList, parameter, rowBounds);
        } finally {
            //移除使用的threadlocal的对象
            if(dialect != null){
                dialect.afterAll();
            }
        }
    }

    /**
     * Spring bean 方式配置时,如果没有配置属性就会执行下面的 setProperties 方法,就不会初始化
     * <p>
     * 因此这里会出现 null 的情况 fixed #26
     */
    private void checkDialectExists() {
        if (dialect == null) {
            synchronized (default_dialect_class) {
                if (dialect == null) {
                    setProperties(new Properties());
                }
            }
        }
    }
    //需要执行count查询,则就需要构造一个重新得mybatis执行的对象
    private Long count(Executor executor, MappedStatement ms, Object parameter,
                       RowBounds rowBounds, ResultHandler resultHandler,
                       BoundSql boundSql) throws SQLException {
        String countMsId = ms.getId() + countSuffix;
        Long count;
        //先判断是否存在手写的 count 查询
        MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
        if (countMs != null) {
            count = ExecutorUtil.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 = ExecutorUtil.executeAutoCount(dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);
        }
        return count;
    }

    /**
     * @Description: mybatis的拦截器代理包装方法
     * @param target 真实对象,通过这个方法进行动态代理的包装
     * @return
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }


    /**
     * @Description: mybatis的拦截器针对加载的拦截器获取的所有属性设置
     * @param properties 针对该拦截器所有设置的属性
     * @return
     */
    @Override
    public void setProperties(Properties properties) {
        //缓存实现,如果不配置,则默认实现guava的cache
        msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
        //获取分页逻辑,如果不实现,则使用默认的pageHelper的分页
        //如果想自己实现,则需要对提供的接口Dialect进行自己的实现
        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);
        //获取count的前缀
        String countSuffix = properties.getProperty("countSuffix");
        if (StringUtil.isNotEmpty(countSuffix)) {
            this.countSuffix = countSuffix;
        }
    }

}

2.3实现流程

3 mybatis对插件的实现

3.1mybatis的框架相关

3.1.1 mybatis的架构

3.1.2 mybatis的流程

3.1.3 myabtis的源码包结构

模块分层定义备注
session接口层会话模块工厂模式
mapping核心处理层映射
builder核心处理层用来各种配置解析建造者模式
scripting核心处理层sql解析
plugin核心处理层扩展插件责任链模式,代理模式
cursor核心处理层游标执行
excutor核心处理层sql执行模板模式
datasource基础支持层数据源模块
cache基础支持层缓存模块装饰者模式
parsing基础支持层XML解析模块
reflection基础支持层反射模块
logging基础支持层日志模块适配器模式
binding基础支持层mapper的绑定代理模式
io基础支持层资源加载模块
type基础支持层类型转换模块
annotations基础支持层注解模块
exceptions基础支持层异常模块

3.2 拦截器实现流程

3.2.1 初始化加载过程

首先在加载mybatis的配置文件来构建sqlSessionFactory,再加载配置文件的时候就会读取plugins的节点。

//sqlSessionFactoryBuilder类中,其中一种构建sqlSessionFactory的方式
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
        //委托XMLConfigBuilder来解析xml文件,并构建
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      //主要再parse方法中进行XML解析
      return build(parser.parse());
    } catch (Exception e) {
        //这里是捕获异常,包装成自己的异常并抛出的idiom?,最后还要reset ErrorContext
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }


  //代码跟读到XMLConfigBuilder#parseConfiguration用来解析mybatis的配置文件
  private void parseConfiguration(XNode root) {
    try {
      //分步骤解析
      //1.properties
      propertiesElement(root.evalNode("properties"));
      //2.类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      //3.插件
      pluginElement(root.evalNode("plugins"));
      //4.对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      //5.对象包装工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6.设置
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      //7.环境
      environmentsElement(root.evalNode("environments"));
      //8.databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //9.类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //10.映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
  //解析插件的节点方法
  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //获取拦截器的包路径
        String interceptor = child.getStringAttribute("interceptor");
        //读取拦截器配置的各种属性,就是再插件中配置的各种值,配置到properties里
        Properties properties = child.getChildrenAsProperties();
        //myabis使用Resource封装的类加载器进行类加载,获取实列
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        //调用setProperties的方法,将值传给插件方,让插件方去处理需要的各种个性化的配置
        interceptorInstance.setProperties(properties);
        //调用InterceptorChain.addInterceptor
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
//configuration.addInterceptor该方法就是将拦截器加入拦截的链中
public class InterceptorChain {

  //内部就是一个拦截器的List
  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
  //对原始对象进行动态代理封装
  public Object pluginAll(Object target) {
    //循环调用每个Interceptor.plugin方法
    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);
  }
}

3.2.2 执行过程

加载过程从上面的流程图首先执行的方法是sqlSessionFactory#openSession方法

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //获取事务相关
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //通过事务工厂来产生一个事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //生成一个执行器(事务包含在执行器里),执行器主要用来进行JDBC的内部封装
      final Executor executor = configuration.newExecutor(tx, execType);
      //然后产生一个DefaultSqlSession,sqlSession主要用来定义各种sql接口,通过构造器参数可以看出来,mybatis的配置,执行器,事务提交
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      //如果打开事务出错,则关闭它
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      //最后清空错误上下文
      ErrorContext.instance().reset();
    }
  }

   //生成执行
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        //防止执行为空
        executorType = executorType == null ? defaultExecutorType : executorType;
        //方式将defaultExecutorType再次设置为空?
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

        Executor executor;
        //然后就是简单的3个分支,产生3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor
        //批处理执行器
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            //重复执行器,缓存statement
            executor = new ReuseExecutor(this, transaction);
        } else {
            //常规执行器最常用
            executor = new SimpleExecutor(this, transaction);
        }
        //如果要求缓存,生成另一种CachingExecutor(默认就是有缓存),装饰者模式,所以默认都是返回CachingExecutor
        if (cacheEnabled) {
            executor = new CachingExecutor(executor);
        }
        //加载插件,插件通过动态代理的方式加载
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }
	
	//interceptorChain.pluginAll最终是对executor的增强实现,会调用Plugin#wrap方法
	public static Object wrap(Object target, Interceptor interceptor) {
    //取得签名Map
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    //取得要改变行为的类(ParameterHandler|ResultSetHandler|StatementHandler|Executor)
    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;
  }

  //取得签名Map
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    //取Intercepts注解,例子可参见ExamplePlugin.java
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    //必须得有Intercepts注解,没有报错
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
    }
    //value是数组型,Signature的数组
    Signature[] sigs = interceptsAnnotation.value();
    //每个class里有多个Method需要被拦截,所以这么定义
    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()]);
  }

//调用查询方法,以sqlSession#selectList
 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //根据statement id找到对应的MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      //转而用执行器来查询结果,注意这里传入的ResultHandler是null
      //pageHelper中的注解标识需要executor,query的方法,此方法正好合适,其实这里的executor
     //已经增强实现了,所以此处调用query首先执行的Plugin#invoke方法 
     return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  //plugin#invoke方法
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //获取所有定义拦截的method
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      //如果需要拦截,则进行拦截
      if (methods != null && methods.contains(method)) {
        //调用Interceptor.intercept,也即实现插件逻辑,分页插件就是再次进行sql改写
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

//使用simpleExecutor为列,最终调用simpleExecutor#doQuery方法,主要是对JDBC的查询流程封装
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //新建一个StatementHandler
      //这里看到ResultHandler传入了
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //准备语句
      stmt = prepareStatement(handler, ms.getStatementLog());
      //StatementHandler.query
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    //先执行Statement.execute,然后resultSetHandler处理结果集
    return resultSetHandler.<E>handleResultSets(statement);
  }

4 思考

      上面是mybatis针对拦截器通过责任链和动态代理的方式进行,我们常用的servlet和dubbo也有拦截器,他们是如何实现的。后续可继续进行相关源码学习内容。