关于java:阅读-MyBatis-源码插件开发

41次阅读

共计 12877 个字符,预计需要花费 33 分钟才能阅读完成。

上一篇文章介绍了 MyBatis 执行 SQL 查问的流程,对源码中的要害类如 Configuration、Executor、StatementHandler 有了肯定意识之后,本篇聊聊 MyBatis 的插件机制。

1. 简介

MyBatis 插件,简略了解为拦截器,它采纳动静代理的形式,实现对指标办法的拦挡,在前后做一些操作。

基于插件机制,基本上能够管制 SQL 执行的各个阶段,如执行阶段,参数解决阶段,语法构建阶段,后果集解决阶段,具体能够依据我的项目业务来实现对应业务逻辑。

反对拦挡的办法:

  1. 执行器 Executor:update、query、commit、rollback 等办法;
  2. 参数处理器 ParameterHandler:getParameterObject、setParameters 等办法;
  3. 后果集处理器 ResultSetHandler:handleResultSets、handleOutputParameters 等方;
  4. SQL 语法构建器 StatementHandler:prepare、parameterize、batch、update、query 等办法;

2. 插件示例

MyBatis XML 配置文件中定义分页插件。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- MyBatis XML 配置阐明 https://mybatis.org/mybatis-3/zh/configuration.html -->
    <plugins>
        <plugin interceptor="com.sumkor.plugin.PageInterceptor"/>
    </plugins>

</configuration>

自定义分页插件 PageInterceptor 代码如下,用于拦挡 Executor#query 办法,批改 MappedStatement 对象中的 SQL 语句。

package com.sumkor.plugin;

import com.sumkor.plugin.page.BoundSqlSqlSource;
import com.sumkor.plugin.page.Page;
import com.sumkor.plugin.page.PageUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.Properties;
import java.util.StringJoiner;

/**
 * 拦挡 Executor#query 办法
 *
 * @author Sumkor
 * @since 2021/7/26
 */
@Intercepts({
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
})
@Slf4j
public class PageInterceptor implements Interceptor {

    private static final int MAPPED_STATEMENT_INDEX = 0;
    private static final int PARAMETER_INDEX = 1;
    private static final int ROW_BOUNDS_INDEX = 2;

    /**
     * 通过反射工具类 MetaObject 来批改 MappedStatement 对象中的 SQL 语句
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {log.info("------------------PageInterceptor#intercept 开始 ------------------");
        final Object[] queryArgs = invocation.getArgs();
        final MappedStatement ms = (MappedStatement) queryArgs[MAPPED_STATEMENT_INDEX];
        final Object parameter = queryArgs[PARAMETER_INDEX];

        // 获取分页参数
        Page pagingParam = PageUtil.getPagingParam();
        try {if (pagingParam != null) {
                // 结构新的分页查问 SQL 字符串
                final BoundSql boundSql = ms.getBoundSql(parameter);
                String pagingSql = getPagingSql(boundSql.getSql(), pagingParam.getOffset(), pagingParam.getLimit());
                BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), pagingSql, boundSql.getParameterMappings(), boundSql.getParameterObject());

                // 通过反射工具类,重置 MappedStatement 中的 SQL 语句
                // MetaObject metaObject = MetaObject.forObject(ms, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
                MetaObject metaObject = SystemMetaObject.forObject(ms);
                metaObject.setValue("sqlSource", new BoundSqlSqlSource(newBoundSql));

                // 重置 RowBound
                queryArgs[ROW_BOUNDS_INDEX] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);
            }
        } catch (Exception e) {log.error("PageInterceptor#intercept 异样", e);
        } finally {log.info("------------------PageInterceptor#intercept 完结 ------------------");
            PageUtil.removePagingParam();}
        return invocation.proceed();}
    
    /**
     * 使得以后插件失效
     */
    @Override
    public Object plugin(Object o) {return Plugin.wrap(o, this);
    }

    /**
     * 结构新的 sql:select xxx from xxx where yyy limit offset,limit
     */
    public String getPagingSql(String sql, int offset, int limit) {StringBuilder result = new StringBuilder(sql.length() + 100);
        result.append(sql).append("limit");

        if (offset > 0) {result.append(offset).append(",").append(limit);
        }else{result.append(limit);
        }
        return result.toString();}
}

通过反射工具类 MetaObject 来批改 MappedStatement 对象中的 SQL 语句,加上 limit m,n 的分页条件。

3. 源码剖析

3.1 解析插件配置

通过 SqlSessionFactoryBuilder 来解析 mybatis-config.xml 文件:

Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

其中会解析 plugins 标签:

org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
org.apache.ibatis.builder.xml.XMLConfigBuilder#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).getDeclaredConstructor().newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

这里会将 Interceptor 的实现类进行实例化,并注册到 Configuration 对象之中的 InterceptorChain 成员变量之中。

org.apache.ibatis.session.Configuration#addInterceptor

  protected final InterceptorChain interceptorChain = new InterceptorChain();

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

InterceptorChain 类残缺内容如下,其中有个 List<Interceptor> 汇合存储注册给 Configuration 对象的插件实例。

org.apache.ibatis.plugin.InterceptorChain

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

}

3.2 插件实现机制

MyBatis 中的 Executor、ParameterHandler、ResultSetHandler、StatementHandler 类均反对插件扩大,而插件的实现机制次要是基于动静代理实现的。

由 Configuration 对象对立治理这些对象的生成。

  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) {return newExecutor(transaction, defaultExecutorType);
  }
  
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 该类型的执行器会批量执行所有更新语句,如果 SELECT 在多个更新两头执行,将在必要时将多条更新语句分隔开来,以不便了解。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);
    }
    // 应用插件来包装 executor
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

3.2.1 原始对象的生成

探索插件的动静代理机制之前,回顾一下须要被代理的原始对象的生成流程:

Executor

开启 SqlSession 会话的时候创立:

org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource

  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); 
      final Executor executor = configuration.newExecutor(tx, execType); // 创立
      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();}
  }

StatementHandler

SqlSession#selectList 执行 SQL 的时候创立:

org.apache.ibatis.session.defaults.DefaultSqlSession#selectList
org.apache.ibatis.executor.CachingExecutor#query
org.apache.ibatis.executor.BaseExecutor#query
org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
org.apache.ibatis.executor.SimpleExecutor#doQuery

  @Override
  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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 创立
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {closeStatement(stmt);
    }
  }

ParameterHandler、ResultSetHandler

在 StatementHandler 的构造函数中创立:

org.apache.ibatis.session.Configuration#newStatementHandler
org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandler
org.apache.ibatis.executor.statement.PreparedStatementHandler#PreparedStatementHandler
org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler

  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql); // 创立 ParameterHandler,反对插件扩大
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql); // 创立 ResultSetHandler,反对插件扩大
  }

3.2.2 代理对象的生成

在 Configuration 类中创立 Executor、ParameterHandler、ResultSetHandler、StatementHandler 对象的时候,都会调用 InterceptorChain#pluginAll 办法来进行插件扩大。

org.apache.ibatis.plugin.InterceptorChain#pluginAll

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

Interceptor 接口中的 plugin 办法默认实现如下:

org.apache.ibatis.plugin.Interceptor#plugin

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

Plugin#wrap 办法的两个入参:

  • target 示意 Executor、ParameterHandler、ResultSetHandler、StatementHandler 这些原始对象。
  • interceptor 是自定义的插件类。

代码流程:

  1. 解析 Interceptor 插件类上的 @Intercepts @Signature 注解。
  2. 依据注解,判断是否是对 target 原始对象的拦挡,是则为 target 原始对象生成代理对象。

org.apache.ibatis.plugin.Plugin#wrap

  public static Object wrap(Object target, Interceptor interceptor) {Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); // 解析插件类上的 @Intercepts @Signature 注解
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) { // 满足条件的,阐明 target 类须要通过插件 interceptor 来拦挡,因而为 target 生成代理
      return Proxy.newProxyInstance(type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap)); // 返回代理对象
    }
    return target; // 返回原始对象
  }

3.2.3 代理对象的应用

以上述的自定义分页插件为例,com.sumkor.plugin.PageInterceptor 类注解申明了对 Executor#query 办法的拦挡,是对 Executor 对象生成动静代理。

org.apache.ibatis.session.defaults.DefaultSqlSession#selectList

因而,对 Executor#query 办法的调用,理论是执行代理对象的办法:

org.apache.ibatis.plugin.Plugin#invoke

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

再进一步调用到自定义的插件类的办法:

com.sumkor.plugin.PageInterceptor#intercept

4. 总结

MyBatis 插件对 Executor、ParameterHandler、ResultSetHandler、StatementHandler 这四个接口上的办法进行拦挡,利用 JDK 动静代理机制,为这些接口的实现类创立代理对象。

在执行办法时,先去执行代理对象的办法,从而执行本人编写的拦挡逻辑。

值得注意的是,Executor、ParameterHandler、ResultSetHandler、StatementHandler 这些对象的生命周期都是 Session 范畴的,每一次开启 SqlSession 会话都会创立新的代理对象。


作者:Sumkor
链接:https://segmentfault.com/a/11…

正文完
 0