概述

Mybatis插件又称拦截器,Mybatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变Mybatis的默认行为。MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis允许使用插件来拦截的方法调用包括:

  • Excutor(update,query,flushStatements,commit,rollback,getTransaction,close,isClosed)拦截器执行的方法
  • ParameterHandler(getParameterObject,setParameters)拦截参数的处理
  • ResultSetHandler(handleResultSets,HandleOutputParameters)拦截参数的处理结果
  • StatementHandler(prepare,parameterize,batch,update,query)拦截sql语法构建的处理

Myabtis四大接口

下图是Mybatis框架的执行过程.Mybatis插件能够对四个对象进行拦截。

  • Executor是Mybatis的内部执行器,它负责调用StatementHandler操作数据库,并把结果集通过ResultSetHandler进行自动映射,另外他还处理二级存储的操作,这里可以看出,我们也是可以通过插件来实现自定义二级缓存的
  • StatementHandler是Mybatis直接和数据库执行sql的对象。另外它也是实现了Mybatis的一级缓存。这里,我们呢可以使用插件来对一级缓存的操作(禁用等)
  • ParameterHandler是Mybatis实现sql注入参数设置的对象。插件可以改变sql的参数默认设置
  • ResultSetHandler是Mybatis把ResultSet集合映射成POJO的接口对象。我们可以定义插件对Mybatis的结果集自动映射进行修改。

拦截器为什么能够拦截

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

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

public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
    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, autoCommit);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

这四个方法实例化了对应的对象之后,都会调用interceptorChain的pluginAll方法,那么下面是pluginAll具体干了什么:

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

从上面的代码可以知道:pluginAll方法就是遍历所有的拦截器(Interceptor的实现类),然后顺序执行我们的插件的plugin方法,一层一层返回我们的原对象(Executor/ParameterHandler/ResultSetHander/StatementHandler)的代理对象,所以实际上我们新建的是这些对象的代理对象,这些对象的代理对象又会关联到拦截器的方法(JDK动态代理的原理相同,见下面的返回代理类的代码)。

public static Object wrap(Object target, Interceptor interceptor) {
   // 获取插件的Intercepts注解
   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;
}

Mybatis的插件都要有Intercepts注解来指定要拦截哪个对象的哪个方法。Plugin.warp方法会返回四大接口的代理对象(通过new Plugin()创建的InvocationHandler处理器),会拦截所有的执行方法。在代理对象执行方法的时候,会调用invocationHandler处理器的invoke方法(invoke方法要筛选执行哪些intecepts方法,intecept方法是增加方法的逻辑)。Mybatis中利用了注解的方式配置指定拦截哪些方法,具体如下:

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

可以看到,只有通过Intecepts注解指定的方法才会执行我们自定义插件的intercept方法,未通过Intecepts注解指定的将不会执行我们的intecept方法。

官方插件开发方式

@Intercepts({@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class TestInterceptor implements Interceptor {
   public Object intercept(Invocation invocation) throws Throwable {
     Object target = invocation.getTarget(); //被代理对象
     Method method = invocation.getMethod(); //代理方法
     Object[] args = invocation.getArgs(); //方法参数
     // do something ...... 方法拦截前执行代码块
     Object result = invocation.proceed();
     // do something .......方法拦截后执行代码块
     return result;
   }
   public Object plugin(Object target) {
     return Plugin.wrap(target, this);
   }
}