mybatis-拦截器

233 阅读5分钟

拦截器

拦截器的使用还是比较简单的,下面就分为应用和原理讲一下:原理直接在Intecepter—>plugin的代码中给说了

拦截器可以用来在某些特定方法执行之前就做一些逻辑修改或者增强,在了解了MyBatis中的各种参数对象的用处之后,就可以通过改变这些对象中的参数来改变最终执行时的SQL语句,这样就达到了改变SQL行为的目的。而具体可以改变的参数就是我们所拦截方法的。

目前拦截器可以拦截四种接口中的方法:Executor、ParameterHandler、ResultSetHandler、StatementHandler,这四个接口中的方法都可以被拦截。

拦截器的使用

现在先来看一下拦截器是如何使用的:

  1. 实现interceptor
  2. 使用@Signature,标注要拦截的方法,@Signature中有三个属性,这三个属性局势用来确定哪一个方法的
    1. 要拦截方法的所属类是什么
    2. 要拦截的目标方法
    3. 要拦截的目标方法的参数,因为会存在重载的情况
package mybatis.study;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;

import java.sql.Connection;
import java.util.Properties;

/**
 * @author daxingxing
 * @date 2023/7/22
 * @description
 */
@Intercepts({
        @Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class})
})
@Slf4j
public class MyBatisInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.info("--我的拦截器插件--before");
        Object proceed = invocation.proceed();
        log.info("--我的拦截器插件--after");
        return proceed;
    }

    @Override
    public Object plugin(Object target) {
        return Interceptor.super.plugin(target);
    }

    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}

Intecepter接口

接口中有三个方法:


public interface Interceptor {

  **#1** Object intercept(Invocation invocation) throws Throwable;

  **#2** default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  **#3** default void setProperties(Properties properties) {
    // NOP
  }

}
  1. intercept:就是给我们提供增强逻辑的,也就主要编写代码的区域,这个方法的中代码会在拦截在@Signature表示的需要拦截的方法。

  2. plugin:表示如何创建这个拦截器,意思就是对于当前要执行拦截的类(就是上述说的4个类)来说,你要怎么样判断当前这个拦截器需要拦截这个类?这里一般不需要我们自己写,只要写死Plugin.wrap()即可:wrap的逻辑也并不复杂

    
    public static Object wrap(Object target, Interceptor interceptor) {
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
    		**这个type属性,就是当前想要进行拦截的类型(会是上面说的4个的其中一个),getAllInterfaces会返回:当前类型的接口中是否存在满足拦截器的条件,就是这个类型要不要拦截,因为我们的拦截器最多可以拦截四种类型,而一个类也可能把这四种类型都实现了因此才会返回一个数组,就是最多会返回四个接口类型:Executor、ParameterHandler、ResultSetHandler、StatementHandler**
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
    			**然后去创建一个代理对象**
          return Proxy.newProxyInstance(
              type.getClassLoader(),
              interfaces,
              new Plugin(target, interceptor, signatureMap));
        }
        return target;
      }
    **如果当前类被拦截器拦截到就创建一个代理对象如果拦截不到就直接返回就好了,但是这里需要注意的一点是,一个类可能会被多个拦截器进行拦截处理,所以上面的wrap将可能会是一个循环调用的处理public StatementHandler newStatementHandler(Executor executor, MappedStatement mapped**Statement, 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;
      }
    **在这个方法中就会将一个一个满足条件的拦截器组成一个调用链**
    **InterceptorChain#pluginAll(statementHandler);**
    public Object pluginAll(Object target) {
    		**在存在多个拦截器的情况下,就会进行循环,而每一个循环的入参就是上一次循环的返回值,因此就会出现层层包装的效果,这也就解释了为什么第一个拦截器会在最后才调用,就是因此它太早的被创建出来成为了拦截器链中最里面的一个拦截器。
    		这样就好像出现了一个为代理创建代理的情况。**
        for (Interceptor interceptor : interceptors) {
          target = interceptor.plugin(target);
        }
        return target;
      }
    
    

**statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);**

而对于这段代码它就还会是:这就是解释了为啥拦截器只会在这个四个接口中有作用,因为MyBatis只在这四种接口创建的时候提供了拦截器的调用入口。

**executor = (Executor) interceptorChain.pluginAll(executor);**

interceptorChain.pluginAll(resultSetHandler);

interceptorChain.pluginAll(parameterHandler);

  1. setProperties:这个就是将配置文件中的拦截器属性,给取出来,变成对象属性的值,就像是这样:

图片转存失败,建议将图片保存下来直接上传

图片转存失败,建议将图片保存下来直接上传

图片转存失败,建议将图片保存下来直接上传

最后:

最后我们只需要将拦截器注册进MyBatis中就可以进行使用了,就是在config.xml的plugins标签中进行配置:

<plugins>
        <plugin interceptor="mybatis.study.MyBatisInterceptor1">
            <property name="name" value="我是拦截器"/>
        </plugin>
        <plugin interceptor="mybatis.study.MyBatisInterceptor2"/>
</plugins>