Mybatis解析-如何编写插件

本文基于mybatis-spring 1.3.1和mybatis 3.4.4版本

mybatis提供了插件,允许我们在mybatis执行过程中的某一点进行拦截调用。默认情况下,MyBatis允许使用插件拦截的方法包括(前面是类名,括号中的是方法名):

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

插件的编写也很简单,只需两步:

  1. 实现Interceptor接口;
  2. 在类上添加注解@Intercepts,在@Intercepts中指定要拦截的类和方法。

下面对上述两步分别介绍。

一、Interceptor接口

插件需要实现Interceptor接口,插件是使用拦截器的方式实现的:

public interface Interceptor {
  //拦截器具体实现,拦截器拦截方法后增加的逻辑都写在intercept方法里面
  Object intercept(Invocation invocation) throws Throwable;
  //创建拦截器的代理类
  Object plugin(Object target);
  //添加属性,当使用XML配置时可以在<plugin/>标签里面添加<property/>
  void setProperties(Properties properties);

}

上面三个方法重点看一下plugin方法。该方法是使拦截器生效的关键。
如果配置插件拦截Executor类的方法,那么mybatis在创建Executor对象后,会调用plugin方法,创建Executor的代理对象,在代理对象中嵌入了调用拦截器intercept方法的逻辑,这样保证了在调用Executor方法前会先调用intercept方法。我们来看一下具体实现(以拦截Executor为例):

  //该方法用于创建Executor对象
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //根据defaultExecutorType配置创建对应的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);
    }
    //遍历拦截器,创建代理对象
    //interceptorChain在mybatis启动的时候识别出所有的拦截器,并将其保存到内部的List属性中
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

下面看一下InterceptorChain类:

public class InterceptorChain {
  //在mybatis启动的时候识别出所有的拦截器,并将其保存到interceptors中
  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  public Object pluginAll(Object target) {
    //遍历所有的拦截器
    for (Interceptor interceptor : interceptors) {
      //调用每个拦截器的plugin方法创建代理对象
      target = interceptor.plugin(target);
    }
    return target;
  }
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }
}

对于创建代理对象,mybatis也为我们提供了现成方法。一般我们实现Interceptor的plugin方法如下:

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

Plugin.wrap方法便是mybatis为我们提供的创建代理的方法。

  public static Object wrap(Object target, Interceptor interceptor) {
    //获取注解配置,这里与Signature注解有关,下文介绍
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      //创建代理对象,在执行目标对象前,先执行Plugin的invoke方法,下面介绍invoke方法
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
  //代理对象在执行目标对象前,先执行下面的invoke方法
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //取出Signature注解的配置,在Signature注解中设置了要拦截的类和方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      //检查被调用的方法是否是需要拦截的方法,如果是则直接执行Interceptor.intercept方法
      //如果不是则执行目标方法
      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);
    }
  }

从上面的介绍可以看到mybatis通过创建代理对象实现了对目标方法的拦截。

二、@Intercepts注解

下面看一下@Intercepts注解:

public @interface Intercepts {
  Signature[] value();
}
public @interface Signature {
  //被拦截的类名
  Class<?> type();
  //被拦截的方法名
  String method();
  //被拦截的方法的入参类型
  Class<?>[] args();
}

Intercepts注解是由Signature数组组成的,Signature注解用于指定被拦截的方法。比如:

@Intercepts({@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})

三、可被拦截的类及方法说明

在本文开始提到了拦截器可以拦截四个类的方法,下面介绍一下这四个类的作用。

1、Executor

Executor,执行器,每个SqlSession对象持有一个执行器,SqlSession将执行SQL语句的功能委托给Executor。Executor主要作用是处理一级缓存和二级缓存、事务提交和回滚、获取数据库连接、调用StatementHandler执行SQL语句。

2、ParameterHandler

ParameterHandler只有一个实现类DefaultParameterHandler。该类setParameters方法的入参是PreparedStatement对象,PreparedStatement中的SQL语句一般有“?”来表示参数,执行SQL语句前需要将参数值设置到PreparedStatement中。因此setParameters方法首先获取参数,然后找到参数类型对应的TypeHandler对象,最后调用TypeHandler.setParameter将参数设置到PreparedStatement对象中。
getParameterObject方法用于返回SQL语句的参数,其实也就是Mapper接口方法的入参。如果入参有多个,使用Map对象表示。

3、StatementHandler

prepare方法作用是创建Statement对象,设置Statement的超时时间和fetchSize;
parameterize方法的入参如果是PreparedStatement对象,则会调用ParameterHandler设置参数,如果是Statement对象,则是空方法;
batch方法入参是Statement对象,调用Statement.addBatch方法将要执行的SQL语句添加批次中;
update方法执行增删改的SQL语句,如果执行insert语句还可以在插入后从数据库中获取插入语句的主键;
query方法执行查询SQL语句,将数据库返回结果传递给ResultSetHandler处理。

4、ResultSetHandler

StatementHandler执行完SQL查询后,将数据库返回结果ResultSet传递给ResultSetHandler对象。ResultSetHandler将结果转换为Mapper接口方法要求的返回值类型。
handleResultSets方法读取ResultSet对象,将数据库返回结果映射到Mapper接口方法要求的返回值类型。
handleOutputParameters方法在CallableStatement场景下使用,本文不做介绍。

上述四个类之间的调用关系如下图:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值