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

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

    最后

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

  • 相关阅读:
    leetcode44:wildcard
    Python实现决策树
    PCA实现
    js触摸事件
    js中的getBoundingClientRect()函数
    Java中timer的schedule()和schedualAtFixedRate()函数的区别
    nodejs中的exports和module.exports
    为什么MySQL数据库要用B+树存储索引
    浅谈微服务中的熔断,限流,降级
    缓存击穿、缓存穿透和缓存雪崩
  • 原文地址:https://www.cnblogs.com/magexi/p/12049161.html
Copyright © 2020-2023  润新知