mybatis plugin的使用

Mybatis允许用户使用自定义拦截器对sql语句执行过程中的某一点进行拦截,默认情况下,Mybatis允许拦截器拦截Executor的方法、ParameterHandler的方法、ResultSetHandler的方法以及StatementHandler的方法。具体可拦截的方法如下:

    1 Executor中的update()、query()、flushStatement()、commit()、rollback()、getTransaction()、close()、isClose()方法

    2 ParameterHandler中的getParameterObject()、setParameter()

    3 ResultSetHandler中的handlerResultSets()、handlerOutputParameter()

    4 StatementHandler中的prepare()、parameterize()、batch()、update()、query()

Mybatis中使用的拦截器都需要实现Interceptor接口。Interceptor接口是Mybatis实现Plugin的核心,其定义如下:

public interface Interceptor {
    //执行拦截逻辑的方法
    Object intercept(Invocation var1) throws Throwable;
    //决定是否触发interceptor()方法
    Object plugin(Object var1);
    //根据配置初始化Interceptor对象
    void setProperties(Properties var1);
}

Mybatis通过拦截器可以改变Mybatis的默认行为,例如实现Sql重写之类的功能。本章将从插件的配置和编写、插件的运行原理,插件注册、执行拦截的时机等多个方面对插件进行介绍。

用户自定义的拦截器除了要继承Interceptor接口,还需要使用@Intercepts和@Signature两个注解进行标识。@Intercepts注解中指定一个@Signature注解列表,每个@Signature注解中都标识了该插件需要拦截的方法信息,其中@Signature注解的type属性指定需要拦截的类型,method属性指定需要拦截的方法,args属性指定了被拦截的方法的参数列表。通过这三个属性值,@Signature注解就可以表示一个方法签名,唯一确定一个方法。如下示例所示,该拦截器需要拦截Executor接口的两个方法,分别是query(MappedStatement, Object, RowBouonds, ResultHandler)方法和close(boolean)方法

@Intercepts({
    @Signature(Type=Executor.class,method="query",args={MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class}),
    @Signature(type=Executor.class, method="close",args={boolean.class})
})
public class ExamplePlugin implement Interceptor{
    private int testProp;
    ......
}

完成一个自定义的拦截器之后,需要在mybaits-config.xml配置文件中对该拦截器进行配置,如下所示

<plugins>
    <plugin interceptor="com.test.ExamplePlugin">
        <property name="testProp",value="100">
    </plugin>
</plugins>

到此为止,一个用户自定义的拦截器就配置好了。在Mybatis初始化时,会通过XMLConfigBuilder.pluginElement()方法解析mybatis-config.xml配置文件中定义的<plugin>节点,得到相应的Interceptor对象以及配置的相应属性,之后会调用Interceptor.setProperties(properties)方法完成对Interceptor对象初始化配置,最后将Interceptor对象添加到Configuration.interceptorChain字段中保存。

完成Interceptor的加载之后,继续介绍Mybatis的拦截器如何对Executor、ParameterHandler、ResultSetHandler、StatementHandler进行拦截。在Mybatis中使用的这四类的对象,都是通过Configuration.new*()系列的方法创建的。如果配置了用户自定义拦截器,则会在该系列方法中,通过InterceptorChina.pluginAll()方法为目标对象创建代理对象,所以通过Configuration.new*()系列方法得到的对象实际上是一个代理对象。

下面以Configuration.newExecutor()方法为例进行分析,Configuration中的newParameterHandler()方法,newResultSetHandler()方法、newStatementHandler()方法原理类似,

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor 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);
    }
    //通过InterceptorChain.pluginAll()方法创建Executor的代理对象
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

InterceptorChain中使用interceptors字段(ArrayList<Interceptor>类型)记录了mybatis-config.xml文件中配置的拦截器。在InterceptorChain.pluginAll()方法中会遍历该interceptors集合,并调用其中的每个元素的plugin()方法创建代理对象,具体的实现如下所示。

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

用户自定义的拦截器的plugin()方法,可以考虑使用Mybatis提供的Plugin工具类实现,它实现了InvocationHandler接口,并提供了一个wrap()静态方法用于创建代理对象。Plugin.wrap()方法的具体实现如下:

    public static Object wrap(Object target, Interceptor interceptor) {
        //获取用户自定义Interceptor中@Signature注解的信息,getSignatureMap()方法负责处理@Signture注解
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        //获取目标类型
        Class<?> type = target.getClass();
        //获取目标类型实现的接口,正如前文所述,拦截器可以拦截4类对象都实现了相应的接口,这也是能使用JDK动态代理的方式创建对象的基础
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        //使用JDK动态代理的方式创建代理对象
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
    }

在Plugin.invoke()方法中,会将当前调用的方法与signatueMap集合中记录的方法信息进行比较,如果当前的方法是需要被拦截的方法,则调用其intercept()方法进行处理,如果不能被拦截则直接调用target的相应方法。Plugin.invoke()方法的具体实现如下:

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //获取当前所在类或集合中,可被当前Interceptor拦截的方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      //如果当前调用的方法需要被拦截,则调用interceptor.intercept()方法进行拦截处理
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      //如果当前方法不能被拦截,则调用target对象的相应方法
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

Interceptor.intercept()方法的参数是Invocation对象,其中封装了目标对象、目标方法以及调用目标方法的参数,并提供了proceed()方法调用目标方法,如下所示。所以在Interceptor.intercept()方法中执行完拦截处理之后,如果需要调用目标方法,则通过Invocation.proceed()方法实现

public Object proceed() throws InvocationTargetException, IllegalAccessException{
  return method.invoke(target, args);
}





猜你喜欢

转载自blog.csdn.net/zongyeqing/article/details/80152616