Mybatis的插件机制简介

插件机制概括

Mybatis插件实现核心:

  JDK动态代理(基于接口),以此织入插件代码逻辑;因此,插件是业务无关的,无感知植入,无形中进行功能增强。

Mybatis允许拦截的方法

Executor
  1. update
  2. query
  3. flushStatements
  4. commit
  5. rollback
  6. getTransaction
  7. close
  8. isClose
ParameterHandler
  1. getParameterObject
  2. setParameter
ResultHandler
  1. handleResultSets
  2. handleOutputParameters
StatementHandler
  1. preper
  2. parameterize
  3. batch
  4. update
  5. query

插件的实现步骤

编写
  1. 创建插件类,实现Interceptor接口
  2. 通过注解标注该插件类的拦截点
插件生效

 配置类中实例化插件

插件示例

 springboot框架下,实现一个简单的分页插件

编写插件类
@Intercepts({
        @Signature(
                type = Executor.class,    // 目标类
                method = "query",    // 目标方法
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
})
public class MySqlPagingPlugin implements Interceptor {

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

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        RowBounds rb = (RowBounds) args[ROW_BOUNDS_INDEX];
        // 无需分页
        if (rb == RowBounds.DEFAULT) {
            return invocation.proceed();
        }

        // 将原 RowBounds 参数设为 RowBounds.DEFAULT,关闭 MyBatis 内置的分页机制
        args[ROW_BOUNDS_INDEX] = RowBounds.DEFAULT;

        MappedStatement ms = (MappedStatement) args[MAPPED_STATEMENT_INDEX];
        BoundSql boundSql = ms.getBoundSql(args[PARAMETER_INDEX]);

        // 获取 SQL 语句,拼接 limit 语句
        String sql = boundSql.getSql();
        String limit = String.format("LIMIT %d,%d", rb.getOffset(), rb.getLimit());
        sql = sql + " " + limit;

        // 创建一个 StaticSqlSource,并将拼接好的 sql 传入
        SqlSource sqlSource = new StaticSqlSource(ms.getConfiguration(), sql, boundSql.getParameterMappings());

        // 通过反射获取并设置 MappedStatement 的 sqlSource 字段
        Field field = MappedStatement.class.getDeclaredField("sqlSource");
        field.setAccessible(true);
        field.set(ms, sqlSource);

        // 执行被拦截方法
        return invocation.proceed();
    }

    //默认方法
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

intercept方法中编写插件增强的功能代码,没有必要可以不重写plugin、setProperties方法,使用Interceptor接口默认实现

配置插件启动生效
@Configuration
public class MybatisPlusConfig {
   @Bean
   public MySqlPagingPlugin paginationInterceptor() {
       MySqlPagingPlugin paginationInterceptor = new MySqlPagingPlugin();
       return paginationInterceptor;
   }

}

启动时实例化一个插件实例即可

插件功能源码分析

以分页插件为分析入口,拦截的Executor类的query方法

执行器Executor的创建

//类 DefaultSqlSessionFactory
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();
    }
}
  
  
//类 Configuration
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;
}
  
  
//类 InterceptorChain
public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
}

//接口Interceptor  plugin方法默认实现
default Object plugin(Object target) {
    return Plugin.wrap(target, this);
}

//类 Plugin生成代理类
public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    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;
}
//类 Plugin执行织入了增强逻辑的切点方法
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);
    }
}

拦截器链示意图
images
执行顺序是由外向内,限制性外层插件逻辑,最后执行Executor

关键方法invoke,首先检测被拦截方法是否配置在插件的@Signature注解中;若是,执行插件逻辑,在执行被拦截方法;若不是,则直接执行被拦拦截方法。

posted @ 2021-01-18 22:27  刘66  阅读(56)  评论(0编辑  收藏  举报