Mybatis反对四种类型的拦截器,这一点能够从Mybatis的初始化类Configuration.java中失去验证(源码体不贴出了,改天剖析Mybatis初始化过程的时候具体说)。具体包含:

  1. ParameterHandler拦截器
  2. ResultSetHandler拦截器
  3. StatementHandler拦截器
  4. Executor拦截器

四种拦截器别离有各自不同的用处,当咱们相熟Mybatis的运行机制之后,了解起来就绝对容易一些。

目前,如果咱们对Mybatis还不是很理解的话,也没有关系,不影响咱们对Mybatis的拦截器做初步的理解。

咱们不须要一次性对四种类型的拦截器都理解,因为他们的工作机制及底层原理大致相同。

咱们明天以Executor拦截器为切入点,理解Mybatis拦截器的实现办法、以及初步剖析其实现原理。

明天的指标是:用Mybatis拦截器技术,计算每一句sql语句的执行时长,并在控制台打印进去具体的sql语句及参数。

在此过程中,咱们会理解:

  1. 编写Mybatis拦截器。
  2. Mybatis拦截器注册。
  3. Mybatis拦截器的初始化过程。
  4. Mybatis拦截器是如何失效的。

筹备工作

Springboot我的项目,并引入Mybatis,pom文件退出依赖:

<dependency>    <groupId>org.mybatis.spring.boot</groupId>     <artifactId>mybatis-spring-boot-starter</artifactId>    <version>2.1.3</version></dependency>

而后配置数据库拜访、建表、创立mapper.xml文件及mapper对象,在mapper.xml中写一个简略的获取数据的sql、应用mapper对象通过该sql语句获取数据。

明天文章的次要指标是拦截器,所以以上对于通过Mybatis获取数据库数据的代码就不贴出了。

编写拦截器

Mybatis拦截器是AOP的一个具体实现,咱们后面文章剖析过AOP的实现原理其实就是动静代理,java实现动静代理有两种形式:cglib和java原生(咱们后面有一篇文章专门剖析过两者的区别),Mybatis拦截器是通过java原生的形式实现的。

其实咱们实现的拦截器在java原生动静代理的框架中属于回调对象的一部分,回调对象其实是Plugin,Plugin对象持有Interceptor,Plugin的invoke办法才是JDK动静代理中的那个回调办法、其中会调用Interceptor的intercept办法,所以Plugin的invoke办法其实又相似于一个模板办法(这部分前面会有具体分析)

所以Mybatis都曾经替咱们安顿好了,咱们的拦截器只须要实现这个intercept办法即可。

@Slf4j@Component@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,        ResultHandler.class}))public class myInterceptor implements Interceptor {    @Override    public Object intercept(Invocation invocation) throws Throwable {        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];        Object param = invocation.getArgs()[1];        BoundSql boundSql = ms.getBoundSql(param);        String sql=boundSql.getSql();        sql=sql.trim().replaceAll("\\s+", " ");        log.info("sql:"+ sql);        log.info("param:" + param);        long startTime=System.currentTimeMillis();        Object result=invocation.proceed();        long endTime=System.currentTimeMillis();        log.info("sql statement take :"+ (endTime - startTime));        return result;    }}

要实现的指标都在下面这段代码中,高深莫测。

须要解释以下几点:

  1. @Intercepts注解:目标是为了通知Mybatis以后拦截器的类型(开篇说的四种类型之一)、拦挡办法名以及办法参数。
  2. Invocation:拦截器被调用的时候组装起来的一个包装对象,蕴含了被代理对象(原对象)、被代理的办法、以及办法调用参数等。
  3. 通过Invocation.proceed()执行被代理对象的原办法,所以在该办法前、后能够增加咱们本人的加强性能,比方计算sql语句执行时长就是在办法执行前、后别离获取零碎工夫并计算时间差即可。
  4. Executor有两个query办法,咱们须要分明地晓得利用最终会调用Executor的哪个query办法,否则如果匹配不上的话就不会执行拦挡。当然,咱们也能够对多个办法执行拦挡。
  5. invocation.getArgs()[0]获取到的是被代理办法的第一个参数,以此类推......能够获取到被代理办法的所有参数,所以在拦截器中能够有残缺的被代理办法的执行现场,能做到一个拦截器实践上能做的任何事件。

好了,拦截器代码咱们就实现了。

拦截器的注册

拦截器编写实现后,须要注册到Mybatis的InterceptorChain中能力失效。

咱们能够看到Mybatis的拦截器又是一个chain的概念,所以咱们是能够实现多个拦截器,每一个拦截器各自实现本人的指标的。

能够通过以下几种形式实现拦截器的注册:

  1. 在mybatis.xml文件中通过plugins标签配置
  2. 通过配置类,创立ConfigurationCustomizer类实现customize办法
  3. Spring我的项目中将拦截器注册到Spring Ioc容器中

咱们以后是基于Springboot的我的项目,所以下面代码中曾经加了@Component注解,通过第3种形式实现注册,简略不便。

运行

拦截器筹备好了,启动我的项目,轻易跑一个数据查问的办法:

能够看到拦截器曾经能够失常工作了。

下面咱们曾经实现了一个简略的Executor拦截器,上面咱们要花点工夫剖析一下这个拦截器是怎么失效的。

拦截器的初始化

在尚未对Mybatis的初始化过程进行整体剖析的状况下,想要彻底搞清楚拦截器的初始化过程多少有点艰难,然而如果咱们只看Mybatis初始化过程中与拦截器无关的局部的话,也不是不能够。

Mybatis初始化的过程中会通过SqlSessionFatoryBuilder创立SqlSessionFactory,SqlSessionFactory会持有Configuration对象。

而咱们后面所说的注册Mybatis拦截器,不管以什么样的形式进行注册,其目标无非就是要让Mybatis启动、初始化的过程中,将拦截器注册到Configuration对象中。

比方咱们下面所说的任何一种注册形式,最终SqlSessionFactoryBean都会将拦截器获取到plugins属性中,在buildSqlSessionFactory()办法中将拦截器注册到Configuration对象中:

if (!isEmpty(this.plugins)) {      Stream.of(this.plugins).forEach(plugin -> {        targetConfiguration.addInterceptor(plugin);        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");      });    }// 省略代码    return this.sqlSessionFactoryBuilder.build(targetConfiguration);

最初调用SqlSessionFactoryBuilder的build办法创立SqlSessionFactory,咱们从源码能够看到最终创立了DefaultSqlSessionFactory,并且将Configuration对象以参数的模式传递过来:

 public SqlSessionFactory build(Configuration config) {    return new DefaultSqlSessionFactory(config);  }

而DefaultSqlSessionFactory会持有该Configuration对象:

 public DefaultSqlSessionFactory(Configuration configuration) {    this.configuration = configuration;  }

所以,Mybatis初始化的过程中会获取到咱们注册的拦截器,该拦截器会注册到Configuration对象中,最终,SqlSesscionFactory对象会持有Configuration对象,从而持有该拦截器。

拦截器是如何失效的#openSession

那咱们当初看一下,曾经实现初始化的拦截器最终是如何失效的。

咱们晓得一条数据库操作语句的执行首先是要调用SqlSesscionFactory的openSession来获取sqlSession开始的。

下面咱们曾经看到初始化过程中创立的是DefaultSqlSessionFactory,所以咱们间接看DefaultSqlSessionFactory的openSession办法。

最终会调用到openSessionFromDataSource或openSessionFromConnection,两个办法的构造差不太多,然而具体细节的辨别明天就不做剖析了。咱们间接看openSessionFromDataSource:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {    Transaction tx = null;    try {      final Environment environment = configuration.getEnvironment();      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);      final Executor executor = configuration.newExecutor(tx, execType);      return new DefaultSqlSession(configuration, executor, autoCommit);    } catch (Exception e) {      closeTransaction(tx); // may have fetched a connection so lets call close()      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);    } finally {      ErrorContext.instance().reset();    }  }

关注的重点放在final Executor executor = configuration.newExecutor(tx, execType)上,咱们去看一下Configuraton的这个办法:

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

办法最初阶段获取到Excutor后,调用interceptorChain.pluginAll,该办法一一调用拦截器的plugin办法,拦截器的plugin办法调用Plugin的wrap办法:

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

最终通过动静代理的形式,返回该对象的一个代理对象,回调对象为持有原对象、拦截器、拦挡办法签名的Plugin对象。

所以咱们晓得,openSession最终创立的DefaultSqlSession所持有的Executor其实是曾经被拦截器解决过的代理对象。

依据咱们对JDK代理的了解,最终Executor的办法被调用的时候,其实是要回调这个代理对象创立的时候的回调器的invoke办法的,也就是Plugin的invoke办法。

拦截器是如何失效的#Executor执行

下面一节剖析了openSession过程中,Executor代理对象是如何被创立的。

接下来看一下具体的Executor的执行,本例拦挡的是他的query办法。其实咱们曾经晓得query办法执行的时候是要调用Plugin的invoke办法的。

代码其实比较简单:

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

获取到以后Executor对象的所有注册的拦挡办法,比拟以后调用的办法是否为拦挡办法,是的话就调用拦截器的intercept办法......就是咱们本人编写的拦截器的拦挡办法。否则如果以后办法没有配置拦挡的话就调用原办法。

调用拦截器的拦挡办法的时候,创立了一个持有被代理对象target、拦挡办法、拦挡办法的调用参数...等数据的Invocation对象作为参数传进去。这也就是为什么咱们在拦截器办法中能获取到这些数据的起因。

OK...还差一点,就是如果配置了多个代理器的话,调用程序的问题。其实整体比拟起来,Mybatis的源码感觉比Spring的简略了许多,拦截器注册之后在InterceptorChain也就是保留在ArrayList中,所以他自身应该是没有程序的,想要管制调用程序应该还得想其余方法。

上一篇 Spring Security + JWT