Mybatis系列之七 Interceptor

一、思路

  1. 责任链模式
  2. 小例子
  3. 源码分析

二、责任链模式

Mybatis拦截器采用了责任链模式。这里简单讲一下责任链模式的概念,如果想了解更多的话,可以去百度,google搜索责任链即可。
责任链模式:使多个对象都有处理请求的机会,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象串成一条链,并沿着这条链一直传递该请求,直到有对象处理它为止。

三、小例子

在我们讲Mybatis的Interceptor之前,先看一个小例子,作用是去拦截Executor接口的List query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4),每当执行这个方法时,输出一个字符串到控制台 “拦截到了mybatis的BaseExecutor中的方法”

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

    private static final Logger logger = LoggerFactory.getLogger(CustomInterceptor.class);

    @Override
    public Object intercept(final Invocation invocation) throws Throwable {
        System.out.println("拦截到了mybatis的BaseExecutor中的方法");
        return invocation.proceed();
    }

    @Override
    public Object plugin(final Object target) {
        if (target instanceof BaseExecutor || target instanceof CachingExecutor) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(final Properties properties) {

    }
}

下面是配置好了Interceptor之后的执行结果,可以看到在控制台中已经输出两条自定义的字符串了。那么我们就来看看Mybatis是怎么来实现拦截器的。


Interceptor执行结果.png

四、源码分析

例子中的自定义拦截器CustomInterceptor实现了Mybatis定义的Interceptor接口,我们先看一下它的定义。

public interface Interceptor {
  // 具体拦截方法业务逻辑的实现
  Object intercept(Invocation invocation) throws Throwable;
  // 注册当前拦截器到拦截器链中
  Object plugin(Object target);
  // 设置自定义参数,但是mybatis结合Spring之后,可以通过spring自身的bean管理机制设置属性
  void setProperties(Properties properties);
}

看到上面的定义,主要是3部分,一个是拦截器属性的设置(这个就pass了),还有就是注册与具体业务逻辑实现。
我们先看看plugin()方法用在了哪儿?我们发现其实只有InterceptorChain链中的pluginAll()这个方法调用了它。看实现是把入参target利用拦截器包了一层,然后返回包装后的对象。

public class InterceptorChain {
  private final List interceptors = new ArrayList();

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

再看看InterceptorChain中的pluginAll()都有什么地方调用了,换个说法就是Mybatis在哪些地方支持加拦截器?ok,我们看到在Configuration中创建帮助实例化ParameterHandler,ResultSetHandler,StatementHandler,Executor类时都支持拦截器。

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

看完了调用链,我们再看看具体怎么实现这个plugin方法。其实Mybatis已经给我们提供了一个工具类Plugin来帮助我们简单实现。
再看一下我们例子中plugin()方法的实现,是借助了Plugin类的wrap()方法。其实现原理也比较简单,首先先根据我们自定义拦截器注解的配置解析出我们需要拦截哪个类的哪个方法,然后再对target参数生成一个代理对象返回。

public static Object wrap(Object target, Interceptor interceptor) {
    Map, Set> signatureMap = getSignatureMap(interceptor);
    Class type = target.getClass();
    // 解析出我们需要拦截哪个类的哪个方法
    Class[] interfaces = getAllInterfaces(type, signatureMap);
    // 然后利用jdk代理生成一个代理类返回
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

我们可以看到代理对象是new Plugin(target, interceptor, signatureMap));。可以看到在invoke方法中调用了自定义拦截器的interceptor的方法

public class Plugin implements InvocationHandler {

  private Object target;
  private Interceptor interceptor;
  private Map, Set> signatureMap;

  private Plugin(Object target, Interceptor interceptor, Map, Set> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        // 重点来了 !!!!!!!!!!!!!!!!!!!!!!!!
        // 这个方法需要拦截的话就调用拦截器的interceptor方法,来调用我们定义的具体的业务逻辑,也就是输出指定字符串。
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }
}

至此,关于Mybatis拦截器的实现就结束了,有问题的小伙伴欢迎交流。

你可能感兴趣的:(Mybatis系列之七 Interceptor)