欢迎光临
我们一直在努力

MyBatis 插件为什么这么强大?一次看懂拦截器机制

MyBatis 插件为什么这么强大?一次看懂拦截器机制

大家都知道 MyBatis 有一个非常强大的功能:

插件(Plugin)。

很多框架都在用它:

  • MyBatis-Plus 分页插件
  • 数据权限插件
  • SQL 审计插件
  • 多租户插件
  • SQL 性能分析插件

甚至很多公司都会自己开发 MyBatis 插件。

但是问题来了:

一个普通的插件,为什么能修改 SQL?

为什么能拦截查询?

为什么能拿到参数?

为什么还能修改返回结果?

今天我们从源码角度彻底看懂。


先看一个插件

@Intercepts({
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
)
})
public class SqlLogInterceptor implements Interceptor {

@Override
public Object intercept(Invocation invocation) throws Throwable {

System.out.println("SQL执行前");

Object result = invocation.proceed();

System.out.println("SQL执行后");

return result;
}
}

配置后:

<plugins>
<plugin interceptor="com.demo.SqlLogInterceptor"/>
</plugins>

执行查询:

userMapper.selectById(1);

控制台:

SQL执行前
SELECT * FROM user WHERE id=1
SQL执行后

问题来了:

为什么 MyBatis 能在执行 SQL 前后插入逻辑?


MyBatis 插件本质是什么

答案就一句话:

插件本质就是 JDK 动态代理。

源码:

Plugin

打开:

public class Plugin implements InvocationHandler

看到这里其实已经破案了。

它本身就是一个动态代理处理器。

结构如下:

Mapper调用

StatementHandler

Plugin代理对象

真实对象

所以插件并不是修改源码。

而是在外面套了一层代理。


插件是怎么创建出来的

启动 MyBatis 时:

Configuration

里面保存了所有插件:

protected final InterceptorChain interceptorChain
= new InterceptorChain();

注册插件:

configuration.addInterceptor(
new SqlLogInterceptor()
);

最终进入:

InterceptorChain

源码:

private final List<Interceptor> interceptors
= new ArrayList<>();

保存所有插件。


什么时候生成代理

关键源码:

InterceptorChain.pluginAll()

public Object pluginAll(Object target) {

for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}

return target;
}

假设有三个插件:

分页插件

日志插件

权限插件

最终变成:

PermissionProxy

LogProxy

PageProxy

Target

标准责任链模式。


plugin 方法干了什么

进入:

Interceptor

默认实现:

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

继续:

Plugin.wrap()

源码:

Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor)
)

是不是很熟悉?

这就是 JDK 动态代理。

相当于:

Proxy.newProxyInstance(...)

所以插件的核心根本不是什么黑科技。

就是代理模式。


SQL 执行时发生了什么

执行:

userMapper.selectById(1);

最终调用:

StatementHandler.prepare()

但此时拿到的已经不是原始对象。

而是:

Plugin Proxy

进入:

Plugin.invoke()

源码:

public Object invoke(
Object proxy,
Method method,
Object[] args)

这里开始判断:

if (匹配拦截方法) {
return interceptor.intercept(...);
}

匹配成功:

interceptor.intercept(...)

进入我们自己的代码:

public Object intercept(
Invocation invocation)

这里就能:

  • 获取参数
  • 修改参数
  • 修改 SQL
  • 修改结果

Invocation 是什么

很多人第一次写插件:

public Object intercept(
Invocation invocation)

都会疑惑:

这个对象哪里来的?

源码:

new Invocation(
target,
method,
args
)

内部保存:

private final Object target;
private final Method method;
private final Object[] args;

也就是说:

目标对象
执行方法
方法参数

全部给你了。

所以插件拥有极大的操作空间。


invocation.proceed() 干了什么

源码:

public Object proceed() throws Exception {
return method.invoke(target, args);
}

翻译成人话:

继续执行原方法

例如:

public Object intercept(
Invocation invocation)
throws Throwable {

System.out.println("before");

Object result =
invocation.proceed();

System.out.println("after");

return result;
}

执行顺序:

before
真实SQL执行
after

这其实就是典型的 AOP。


为什么分页插件能修改 SQL

以分页插件为例:

拦截:

StatementHandler.prepare()

拿到原始 SQL:

SELECT * FROM user

修改:

SELECT * FROM user
LIMIT 0,10

然后再执行:

invocation.proceed()

最终数据库执行的就是新 SQL。

所以分页插件并没有数据库魔法。

只是提前改写 SQL。


MyBatis 允许拦截哪些对象

官方只允许拦截四种核心对象:

Executor

负责执行 SQL

StatementHandler

负责 SQL 预处理

ParameterHandler

负责参数设置

ResultSetHandler

负责结果集处理

源码:

Plugin.getSignatureMap()

里面会校验。

如果不是这四种:

无法代理


Debug 一次完整流程

Mapper代理对象

Executor

StatementHandler

Plugin.invoke()

Interceptor.intercept()

Invocation.proceed()

真实SQL执行

返回结果

看到这里你会发现:

MyBatis 插件机制和 Spring AOP 非常像。

都是:

代理

拦截

增强

放行

区别只是:

Spring 拦截的是 Bean。

MyBatis 拦截的是 SQL 执行过程。


总结

MyBatis 插件看起来很神奇。

其实核心只有一句话:

利用 JDK 动态代理,
在 SQL 执行链路中插入自定义逻辑。

源码关键类:

Interceptor
Plugin
Invocation
InterceptorChain

执行流程:

InterceptorChain

Plugin.wrap

JDK动态代理

Plugin.invoke

Interceptor.intercept

Invocation.proceed

真实SQL执行

理解这一套之后:

  • MyBatis-Plus 分页插件
  • 多租户插件
  • SQL审计插件
  • 数据权限插件

基本都能看懂了。


上篇文章:

《MyBatis 动态 SQL 为什么能如此灵活?》

下篇文章:

《为什么很多公司禁用 MyBatis 二级缓存?》

赞(0)
未经允许不得转载:171主机测评 » MyBatis 插件为什么这么强大?一次看懂拦截器机制
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址