Mybatis反对四种类型的拦截器,这一点能够从Mybatis的初始化类Configuration.java中失去验证(源码体不贴出了,改天剖析Mybatis初始化过程的时候具体说)。具体包含:
- ParameterHandler拦截器
- ResultSetHandler拦截器
- StatementHandler拦截器
- Executor拦截器
四种拦截器别离有各自不同的用处,当咱们相熟Mybatis的运行机制之后,了解起来就绝对容易一些。
目前,如果咱们对Mybatis还不是很理解的话,也没有关系,不影响咱们对Mybatis的拦截器做初步的理解。
咱们不须要一次性对四种类型的拦截器都理解,因为他们的工作机制及底层原理大致相同。
咱们明天以Executor拦截器为切入点,理解Mybatis拦截器的实现办法、以及初步剖析其实现原理。
明天的指标是:用Mybatis拦截器技术,计算每一句sql语句的执行时长,并在控制台打印进去具体的sql语句及参数。
在此过程中,咱们会理解:
- 编写Mybatis拦截器。
- Mybatis拦截器注册。
- Mybatis拦截器的初始化过程。
- 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; }}
要实现的指标都在下面这段代码中,高深莫测。
须要解释以下几点:
- @Intercepts注解:目标是为了通知Mybatis以后拦截器的类型(开篇说的四种类型之一)、拦挡办法名以及办法参数。
- Invocation:拦截器被调用的时候组装起来的一个包装对象,蕴含了被代理对象(原对象)、被代理的办法、以及办法调用参数等。
- 通过Invocation.proceed()执行被代理对象的原办法,所以在该办法前、后能够增加咱们本人的加强性能,比方计算sql语句执行时长就是在办法执行前、后别离获取零碎工夫并计算时间差即可。
- Executor有两个query办法,咱们须要分明地晓得利用最终会调用Executor的哪个query办法,否则如果匹配不上的话就不会执行拦挡。当然,咱们也能够对多个办法执行拦挡。
- invocation.getArgs()[0]获取到的是被代理办法的第一个参数,以此类推......能够获取到被代理办法的所有参数,所以在拦截器中能够有残缺的被代理办法的执行现场,能做到一个拦截器实践上能做的任何事件。
好了,拦截器代码咱们就实现了。
拦截器的注册
拦截器编写实现后,须要注册到Mybatis的InterceptorChain中能力失效。
咱们能够看到Mybatis的拦截器又是一个chain的概念,所以咱们是能够实现多个拦截器,每一个拦截器各自实现本人的指标的。
能够通过以下几种形式实现拦截器的注册:
- 在mybatis.xml文件中通过plugins标签配置
- 通过配置类,创立ConfigurationCustomizer类实现customize办法
- 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