MyBatis源码阅读——MyBatis插件原理

前言

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括。

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。

使用插件

功能描述:编写一个MyBatis插件,在执行删除动作的时候,做日志记录

实现过程:

编写类DeleteWarningPlugin,实现Interceptor 接口

@Intercepts({@Signature(
        type = Executor.class,
        method = "update",
        args = {MappedStatement.class, Object.class})})
public class DeleteWarningPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        System.out.println();
        if ("DELETE".equals(ms.getSqlCommandType().name())) {
            SqlSource sqlSource = ms.getSqlSource();
            RawSqlSource rawSqlSource = (RawSqlSource) sqlSource;
            MetaObject metaObject = SystemMetaObject.forObject(rawSqlSource);
            SqlSource thisSqlSource = (StaticSqlSource) metaObject.getValue("sqlSource");
            MetaObject metaObjectSql = SystemMetaObject.forObject(thisSqlSource);
            String sql = (String) metaObjectSql.getValue("sql");
            System.out.println("系统发生删除操作:" + sql);
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        for (String key : properties.stringPropertyNames()) {
            System.out.println(key + " " + properties.get(key));
        }
    }
}

在mybatis-config.xml配置中加入插件

 
        
            
        
        
            
        
    

运行调试:

public class Demo6PluginExample {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis/conf/mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //从 XML 中构建 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession(true);
        try {
            BlogMapper mapper = session.getMapper(BlogMapper.class);
            int result = mapper.delete(25L);
            System.out.println("result: " + result);
        } finally {
            session.close();
        }
    }
}

调试结果:

someProperty 100
系统发生删除操作:delete from blog where blog_id = ?
result: 1

可以看到,插件已经起了效果。

插件原理

我们拿Executor 来说,
MyBatis源码阅读——MyBatis插件原理_第1张图片
打开源码org.apache.ibatis.session.Configuration->newExecutor()

  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 = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

打开InterceptorChain源码

public class InterceptorChain {

  private final List 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 getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

org.apache.ibatis.session.Configuration->newExecutor() 何时调用?顺序如下
SqlSession session = sqlSessionFactory.openSession();
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();
    }
  }

Plugin

打开Plugin这个类我们可以看到,这其实是一个动态代理

  public static Object wrap(Object target, Interceptor interceptor) {
    Map, Set> 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;
  }

你可能感兴趣的:(Java,MyBatis源码阅读)