哈哈哈,今天又拖更了!!
废话不多说,直接进入主题,这个Mybatis插件机制有点像Spring MVC的拦截器,底层的設計模式都是拦截器模式。
我们在编写插件时,除了需要让插件类实现 Interceptor 接口,还需要通过注解标注该插件的拦截点。所谓拦截点指的是插件所能拦截的方法,MyBatis 所允许拦截的方法如下:
如果我们想要拦截 Executor 的 query 方法,那么可以这样定义插件。
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
)
})
除此之外,我们还需将插件配置到相关文件中。这样 MyBatis 在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链)中。待准备工作做完后,MyBatis 处于就绪状态。我们在执行 SQL 时,需要先通过 DefaultSqlSessionFactory 创建 SqlSession 。Executor 实例会在创建 SqlSession 的过程中被创建,Executor 实例创建完毕后,MyBatis 会通过 JDK 动态代理为实例生成代理类。这样,插件逻辑即可在 Executor 相关方法被调用前执行。
以上就是 MyBatis 插件机制的基本原理。
从源头开始:
// DefaultSqlSessionFactory
1.
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
2.
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
// 省略
// 创建 Executor
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();
}
}
3.
// -☆- Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 根据 executorType 创建相应的 Executor 实例
if (ExecutorType.BATCH == executorType) {...}
else if (ExecutorType.REUSE == executorType) {...}
else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 植入插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
// newExecutor 方法在创建好 Executor 实例后,紧接着通过拦截器链 interceptorChain 为 Executor 实例植入代理逻辑。
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
// 遍历拦截器集合
for (Interceptor interceptor : interceptors) {
// 调用拦截器的 plugin 方法植入相应的插件逻辑
target = interceptor.plugin(target);
}
return target;
}
/** 添加插件实例到 interceptors 集合中 */
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
/** 获取插件列表 */
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
它的 pluginAll 方法会调用具体插件的 plugin 方法植入相应的插件逻辑。如果有多个插件,则会多次调用 plugin 方法,最终生成一个层层嵌套的代理类。
当 Executor 的某个方法被调用的时候,插件逻辑会先行执行。执行顺序由外而内,比如上图的执行顺序为 plugin3 → plugin2 → Plugin1 → Executor
。
// 这个类SqlCostTimeInterceptor是我自定义实现的,稍后会说
// -☆- SqlCostTimeInterceptor
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// -☆- Plugin
public static Object wrap(Object target, Interceptor interceptor) {
/*
* 获取插件类 @Signature 注解内容,并生成相应的映射结构。形如下面:
* {
* Executor.class : [query, update, commit],
* ParameterHandler.class : [getParameterObject, setParameters]
* }
*/
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 获取目标类实现的接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 通过 JDK 动态代理为目标类生成代理类
// Plugin 类实现了 InvocationHandler 接口,因此它可以作为参数传给 Proxy 的 newProxyInstance 方法
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
再看看我自定义类上的@Signature
注解:
@Intercepts({
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})})
很明显type
对应着Map中的key
,method
对应着value
植入完之后,就要执行插件逻辑了!Plugin 实现了 InvocationHandler 接口,因此它的 invoke 方法会拦截所有的方法调用。invoke 方法会对所拦截的方法进行检测,以决定是否执行插件逻辑。
// -☆- Plugin
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
/*
* 获取被拦截方法列表,比如:
* signatureMap.get(Executor.class),可能返回 [query, update, commit]
*/
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);
}
}
// 插件逻辑封装在 intercept 中,该方法的参数类型为 Invocation。Invocation 主要用于存储目标类,方法以及方法参数列表。下面简单看一下该类的定义
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
// 省略部分代码
public Object proceed() throws InvocationTargetException, IllegalAccessException {
// 调用被拦截的方法
return method.invoke(target, args);
}
}
好了,插件逻辑就是这样子,就是JDK动态代理!!(迟点再出一篇动态代理的文章)
// 先定义一个拦截器
@Intercepts({
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})})
public class SqlCostTimeInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
long startTime = System.currentTimeMillis();
StatementHandler statementHandler = (StatementHandler) target;
try {
return invocation.proceed();
} finally {
long costTime = System.currentTimeMillis() - startTime;
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
System.out.println("执行 SQL:" + sql + "执行耗时:" + costTime + "ms");
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的信息:"+properties);
}
}
配置信息:
<plugins>
<plugin interceptor="Interceptor.MySqlPagingPlugin">plugin>
plugins>
说一下算法的进展吧,额,进展得不错,今晚也顺利地刷了算法,哈哈哈,2个小时刷了2道题都是media(作为算法小白的我,已经很满足了,要加油啊!!!),接下来就打算复盘一下之前学过的基础知识,但是也没那么好进行复盘了,感觉这个基础知识是最难搞的,平时做项目可能又用不到,有些知识点又被问得比较频繁,接下来这段时间,基础知识的复盘可能会以一种面试题的形式进行吧。不过最近也在搞Tomcat源码,这里面也包含了很多基础知识,还是老师那句话。结合项目更容易理解,今天看Tomcat源码,也发现了线程池的应用和设计模式的应用,感觉比起以前单纯看知识点更容易理解了!!
谈谈我大学遇到的那个她(这里先说结论,我们最后是没有在一起的)下篇,建议先看看上篇
先从近开始说起吧!!
我们最后一次说话,时间应该定格在11月21号,那天是他的生日,其实在这之前我已经表白了,不过失败了,他说,我们还可以做朋友!(我缺的是朋友吗?hhh,我缺的是一个懂我理解我的女朋友)
为什么会一直拖到她生日那天呢,主要是我之前生日,他给我送了个蛋糕,还在操场等我下课一起吃蛋糕,所以,我觉得要礼尚往来吧,所以怎么样也要准备一份生日礼物吧,我那时候很喜欢拍视频,所以就约她提前一周出来玩,我就利用这一周搞个视频出来,感兴趣的朋友可以看看,不过也不是什么大神制作(不好看勿喷,纯业余)!这也是我拍的最后一个视频,从那以后,我好像也好久没拿起手机拍视频,拍照片了!
如上图,我现在还不明白什么是作为好朋友的喜欢,总之一句话吧,他就是不喜欢我,哈哈哈,第一次表白,还是以视频的方式表白了,感觉自己已经做好充分的准备了,也没遗憾了!!
好了,就说到这吧!最近有朋友问我,半年都没见我一条朋友圈,不是因为我懒,因为朋友圈天太多认识的人啦,很多真实的感受都不能表达出来,所以就换个平台来释放自己的表达欲!
时间也差不多了,各位老铁,晚安!!!
提醒:我每篇文章后面都会有个人唠叨,都会分享自己的一些生活经验等等,希望自己能做一个不至于技术的博主,加油哟!