MyBatis 插件

MyBatis 提供了插件的机制,让开发人员可以对 SQL 执行的几个关键过程进行自定义的特殊处理,实现原理依然是 JDK 动态代理。

还是以一个简单的例子开始(MyBatis 3.4.0):

/**
 *
 * @author xi
 * @date 2018/10/01 14:12
 */
public class Demo {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream is = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
        Role role = roleMapper.getRole(1L);
        System.out.println(role);
    }
}

插件的代码先放上,具体作用后面用到再回头看:

/**
 * @author xi
 * @date 2019/9/27 21:11
 */
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class MyPlugin implements Interceptor {
    private Properties props;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("before...");
        Object result = invocation.proceed();
        System.out.println("after...");
        return result;
    }

    @Override
    public Object plugin(Object target) {
        System.out.println("create proxy");
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        System.out.println(properties.get("dbType"));
        props = properties;
    }
}

插件的配置:

<plugins>
    <plugin interceptor="com.learn.plugin.MyPlugin">
        <property name="dbType" value="mysql"/>
    </plugin>
</plugins>

1. 插件解析

从 Demo 中的org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)方法,一步步跟进到org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration方法:

private void parseConfiguration(XNode root) {
  try {
    Properties settings = settingsAsPropertiess(root.evalNode("settings"));
    //issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    loadCustomVfs(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectionFactoryElement(root.evalNode("reflectionFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

很明显能看到 plugins 配置的解析,看一下解析方法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).newInstance();
      interceptorInstance.setProperties(properties);
      configuration.addInterceptor(interceptorInstance);
    }
  }
}

很明显是通过反射的方式创建插件的实例对象,然后调用Configuration对象的org.apache.ibatis.session.Configuration#addInterceptor方法:

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

InterceptorChain对象实际上里面就是个ArrayList对象,到这插件就初始化完毕了。

2. 插件工作流程

插件主要对 4 类关键对象起作用:

  1. Executor
  2. StatementHandler
  3. ParameterHandler
  4. ResultSetHandler

这几类对象大致功能从名称就能看出来,主要说下ExecutorSqlSession对象是通过Executor对象来对数据库进行操作的。参考《深入理解mybatis原理》 MyBatis的架构设计以及实例分析 - 我的程序人生(亦山札记) - CSDN博客 中 MyBatis 层次结构图:

2.1 Executor

本次分析一个简单的 SQL 执行过程,找到插件是如何起作用的。回到 Demo 代码中获取SqlSession对象的这行代码:SqlSession sqlSession = sqlSessionFactory.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);// 创建 executor
    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();
  }
}

在这个方法中创建了Executor对象,至于这个方法中做的其他的事情不是本次关注的重点,不必过分关注。进入org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)方法:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  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);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);// 通过拦截器生成代理对象
  return executor;
}

先创建了特定类型的Executor对象,暂时以SimpleExecutor为主。这里需要注意的是在Executor对象返回之前,调用了拦截器链的org.apache.ibatis.plugin.InterceptorChain#pluginAll方法,是时候放出它的代码:

/**
 * @author Clinton Begin
 */
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

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

}

这个方法很简单,就是遍历拦截器链。需要注意的是每次拦截器处理后返回的对象(代理对象),都会交给下一个拦截器处理,所以这是一个层层嵌套的代理对象,有好几个中间商赚差价。org.apache.ibatis.plugin.Interceptor#plugin是一个接口方法,放出拦截器接口的代码:

/**
 * @author Clinton Begin
 */
public interface Interceptor {

  // 
  Object intercept(Invocation invocation) throws Throwable;

  // 创建 target 对象的代理
  Object plugin(Object target);

  // 解析配置文件时,用来设置属性
  void setProperties(Properties properties);

}

前面给出的自定义的插件实现的就是这个接口,接口中各个方法的具体作用见注释,先继续往下走。

MyPluginplugin(Object target)方法主要关注:Plugin.wrap(target, this);

public static Object wrap(Object target, Interceptor interceptor) {
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);// 解析 MyPlugin 类上的注解
  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;// 没有实现接口则直接返回 target
}

注释中已经说明了该方法的作用,这里主要关注一下Plugin类。它实现了InvocationHandler接口,所以需要看一下它的org.apache.ibatis.plugin.Plugin#invoke方法:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {// signatureMap 是前面 wrap 时解析出来的
    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);
  }
}

具体操作就是 JDK 动态代理那一套。这里有个Invocation对象,其实就是封装了一层对原始对象的方法调用。回头看自定以的插件的intercept方法:

@Override
public Object intercept(Invocation invocation) throws Throwable {
    System.out.println("before...");
    Object result = invocation.proceed();// 调用原始对象的方法
    System.out.println("after...");
    return result;
}

插件在原始方法调用前后,做一些特殊的处理。注意Invocation对象里面保存了三个成员变量,分别是:原始对象、被拦截的方法、被拦截方法的参数。Invocation类对外提供了获取和修改这 3 个成员变量的方法。

至此,一个完整的插件生效的流程就结束了。插件对其他几个对象的处理也是类似的流程。具体流程就不一一分析了,主要找到生成拦截器链的地方。

2.2 StatementHandler、ParameterHandler、ResultHandler

拦截器链都是在创建指定对象之后,就对原始对象创建代理对象,这些方法都在Configuration类里面。

先看StatementHandler对象的创建方法:org.apache.ibatis.session.Configuration#newStatementHandler

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

很明显可以看到拦截器链的创建,具体内容和前面一样。在 IDEA 中可以用Command + B或者Ctrl + B查看这个创建方法被使用的地方,如下图:

可以看到都是在Executor对象中调用的,这里以SimpleExecutor中的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.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

创建StatementHandler对象就是在Executor准备执行 SQL 的时候。

回头我们再看看StatementHandler的创建,找到RoutingStatementHandler的构建方法:

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

  switch (ms.getStatementType()) {
    case STATEMENT:
      delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case PREPARED:
      delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case CALLABLE:
      delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    default:
      throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
  }

}

从名称可以知道是个路由,根据类型创建指定的StatementHandler对象。3 个子类的构造方法都会调用父类的构造方法: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);
  this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

这个方法的前面几行暂时不看,只用看最后两行。回到Configuration类里面看看两个创建对应 Handler 的方法:

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

跟前面创建拦截器链的代码都类似,就不一一细说。

最后

附上拦截器相关的类图: 右键查看高清大图

原文地址:https://www.cnblogs.com/magexi/p/12049161.html