MyBatis 源码分析 - 插件机制

除此之外,我们还需将插件配置到相关文件中。这样 MyBatis 在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链)中。待准备工作做完后,MyBatis 处于就绪状态。我们在执行 SQL 时,需要先通过 DefaultSqlSessionFactory 创建 SqlSession 。Executor 实例会在创建 SqlSession 的过程中被创建,Executor 实例创建完毕后,MyBatis 会通过 JDK 动态代理为实例生成代理类。这样,插件逻辑即可在 Executor 相关方法被调用前执行。

以上就是 MyBatis 插件机制的基本原理。接下来,我们来看一下原理背后对应的源码是怎样的。

3. 源码分析


3.1 植入插件逻辑

本节,我将以 Executor 为例,分析 MyBatis 是如何为 Executor 实例植入插件逻辑的。Executor 实例是在开启 SqlSession 时被创建的,因此,下面我们从源头进行分析。先来看一下 SqlSession 开启的过程。

// -☆- DefaultSqlSessionFactory

public SqlSession openSession() {

return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);

}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {

Transaction tx = null;

try {

// 省略部分逻辑

// 创建 Executor

final Executor executor = configuration.newExecutor(tx, execType);

return new DefaultSqlSession(configuration, executor, autoCommit);

}

catch (Exception e) {…}

finally {…}

}

Executor 的创建过程封装在 Configuration 中,我们跟进去看看看。

// -☆- 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 实例植入代理逻辑。那下面我们看一下 InterceptorChain 的代码是怎样的。

public class InterceptorChain {

private final List interceptors = new ArrayList();

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 getInterceptors() {

return Collections.unmodifiableList(interceptors);

}

}

以上是 InterceptorChain 的全部代码,比较简单。它的 pluginAll 方法会调用具体插件的 plugin 方法植入相应的插件逻辑。如果有多个插件,则会多次调用 plugin 方法,最终生成一个层层嵌套的代理类。形如下面:

img

当 Executor 的某个方法被调用的时候,插件逻辑会先行执行。执行顺序由外而内,比如上图的执行顺序为 plugin3 → plugin2 → Plugin1 → Executor

plugin 方法是由具体的插件类实现,不过该方法代码一般比较固定,所以下面找个示例分析一下。

// -☆- ExamplePlugin

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> signatureMap = getSignatureMap(interceptor);

Class<?> type = target.getClass();

// 获取目标类实现的接口

Class<?>[] interfaces = getAllInterfaces(type, signatureMap);

if (interfaces.length > 0) {

// 通过 JDK 动态代理为目标类生成代理类

return Proxy.newProxyInstance(

type.getClassLoader(),

interfaces,

new Plugin(target, interceptor, signatureMap));

}

return target;

}

如上,plugin 方法在内部调用了 Plugin 类的 wrap 方法,用于为目标对象生成代理。Plugin 类实现了 InvocationHandler 接口,因此它可以作为参数传给 Proxy 的 newProxyInstance 方法。

到这里,关于插件植入的逻辑就分析完了。接下来,我们来看看插件逻辑是怎样执行的。

3.2 执行插件逻辑

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

}

}

invoke 方法的代码比较少,逻辑不难理解。首先,invoke 方法会检测被拦截方法是否配置在插件的 @Signature 注解中,若是,则执行插件逻辑,否则执行被拦截方法。插件逻辑封装在 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);

}

}

关于插件的执行逻辑就分析到这,整个过程不难理解,大家简单看看即可。

4. 实现一个分页插件


为了更好的向大家介绍 MyBatis 的插件机制,下面我将手写一个针对 MySQL 的分页插件。Talk is cheap. Show the code。

@Intercepts({

@Signature(

type = Executor.class, // 目标类

method = “query”, // 目标方法

args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}

)

})

public class MySqlPagingPlugin implements Interceptor {

private static final Integer MAPPED_STATEMENT_INDEX = 0;

private static final Integer PARAMETER_INDEX = 1;

private static final Integer ROW_BOUNDS_INDEX = 2;

@Override

public Object intercept(Invocation invocation) throws Throwable {

Object[] args = invocation.getArgs();

RowBounds rb = (RowBounds) args[ROW_BOUNDS_INDEX];

// 无需分页

if (rb == RowBounds.DEFAULT) {

return invocation.proceed();

}

// 将原 RowBounds 参数设为 RowBounds.DEFAULT,关闭 MyBatis 内置的分页机制

args[ROW_BOUNDS_INDEX] = RowBounds.DEFAULT;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

分布式技术专题+面试解析+相关的手写和学习的笔记pdf

还有更多Java笔记分享如下:

image

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

分布式技术专题+面试解析+相关的手写和学习的笔记pdf

还有更多Java笔记分享如下:

[外链图片转存中…(img-JWQGTu4X-1712046005809)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值