mybatis Interceptor使用和原理分析

  我们在使用mybatis的过程中可能遇到一些通用的需求比如分页等,我们需要统一拦截一些方法,然后完成操作.mybatis为我们提供了Interceptor接口做这件事.但是mybatis的Interceptor只能拦截mybatis中指定的类和方法,并不能拦截所有的方法.

使用

  我们只需要写一个类,然后继承mybatis的Interceptor方法,然后使用@Intercepts注解说明需要拦截的类和方法即可.可以在@Intercepts中配置多个类和方法,用于拦截多个方法.然后把我们定义的类定义到mybatis的配置文件的<plugins><plugin>元素中.

  mybatis在解析配置文件的时候会自动的为我们生成代理,拦截我们需要拦截的方法.

  mybatis中只能拦截下面类中的方法,其他的都不会进行拦截

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

  

//使用@Intercepts定义需要拦截的类,和方法,并且指明方法的入参(因为再mybatis中有很多重载的方法)
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
        RowBounds.class, ResultHandler.class})})
//继承Interceptor接口,并且实现intercept方法.
public class PagePlugin implements Interceptor {
    
    /**
     * intercept方法会有一个Invocation参数,里面封装了原始的被调用的方法名称和方法参数,
     * 我们可以直接使用Invocation进行获取,如果需要继续执行则直接调用Invocation的proceed方法即可
     */
    @Override
    public Object intercept(Invocation iv) throws Throwable {
        Method method = iv.getMethod();
        Object[] args = iv.getArgs();
        return iv.proceed();
    }
}
View Code

 

原理

  我们知道了怎么使用mybatis的interceptor下面说一下mybatis的interceptor是怎么生效的.

  1.读取,

    我们在mybatis配置文件的整体加载过程一文中提到,在XmlConfigBuilder.parseConfiguration方法中会解析配置文件中的plugins元素.读取其所有的子元素,然后根据interceptor属性找到真正的拦截器,并调用默认的构造方法对其实例化,然后调用Interceptor.setProperties方法设置属性,并且全部保存到InterceptorChain中.

  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
View Code

 

  2.代理

    因为mybatis中的Interceptor只会拦截Executor,ParameterHandler,ResultHandler,StatementHandler四个类,所以mybatis只需要在创建这四个类的时候生成代理就行了,而这四个类的生成都是mybatis通过Configuration生成的,所以mybatis只需要在创建这四个类的地方生成代理就可以了.

    这里以Executor为例子说明一下mybatis生成代理的过程.Configuration.newExecutor方法生成Executor的时候会调用interceptorChain的pluginAll方法,对新创建的对象生成代理.

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

    在pluginAll方法中,会遍历mybatis已经加载的所有的Interceptor,然后调用Interceptor.plugin方法生成代理.

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

    在Interceptor.plugin中只有一行代码,就是调用了Plugin.wrap(object,Interceptor),在wrap方法中,mybatis创建了plugin对象,而plugin类继承了InvocationHandler接口.在plugin中的invoke方法中,根据@interceptor注解中的配置,选择之际额调用targer的方法还是调用interceptor的intercept方法.

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

  @Override
  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);
    }
  }
View Code

 

posted on 2022-08-25 22:03  monkeydai  阅读(1940)  评论(0编辑  收藏  举报

导航