Mybatis源码解析:强大的插件是如何工作的?

在这里插入图片描述

框架扩展的几种实现方式

我们都知道业务场景是多变的,框架不可能对每种业务场景都进行支持,因此框架需要预留一些扩展点,让用户利用这些扩展点来增强功能。这些增强的功能称为插件,在当前软件设计中插件随处可在(例如我们常用的chrome,vscode,notepad++等都提供了大量实用的插件,让软件变的更好用)

如果让你对原有的功能进行增强,你会想到哪种方式?

责任链模式,代理模式,装饰者模式等其实都可以的。
Servlet Filter和Spring MVC Interceptor是用的责任链模式
Dubbo Filter同时用了装饰者模式和责任链模式
Mybaits Interceptor用了责任链模式和动态代理

动态代理可以对SQL语句执行过程中的某一点进行拦截,当配置多个插件时,责任链模式可以进行多次拦截,责任链模式的UML图如下
在这里插入图片描述
可以看到在一条责任链中,每个Handler对象都包含对下一个Handler对象的引用,一个Handler对象处理完消息会把请求传给下一个Handler对象继续处理,以此类推,直至整条责任链结束。这时我们可以改变Handler的执行顺序,增加或者删除Handler,符合开闭原则

写一个耗时统计插件

我们先写一个打印SQL执行时间的插件,只需要实现Interceptor接口,用@Signature指定要增强的方法,重写intercept方法写增强逻辑即可

@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 {

    public static final Logger logger = LoggerFactory.getLogger(SqlCostTimeInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        long start = System.currentTimeMillis();
        try {
            // 执行被拦截的方法
            return invocation.proceed();
        } finally {
            BoundSql boundSql = statementHandler.getBoundSql();
            String sql = boundSql.getSql();
            long end = System.currentTimeMillis();
            long cost = end - start;
            logger.info("{}, cost is {}", sql, cost);
        }
    }
}

在mybatis配置文件中配置插件

<plugins>
	<plugin interceptor="com.javashitang.part1.plugins.SqlCostTimeInterceptor"></plugin>
</plugins>

此时就可以打印出执行的SQL和耗费的时间,效果如下

select id, role_name as roleName, note from role where id = ?, cost is 35

可以看到还是挺简单的,我们来分析一下具体的实现

源码解析

上面说到我们手写插件只需要实现Interceptor接口即可,我们来看看Interceptor接口在哪使用了

在这里插入图片描述
InterceptorChain类一看就是责任链模式的实现,addIntercepto是增加插件,而pluginAll是利用插件对原有功能进行增强(我们看Interceptor#plugin方法确认一下)

在这里插入图片描述
在plugin方法中调用了Plugin#wrap方法,我们接着追
在这里插入图片描述
看到Proxy.newProxyInstance我们就可以确定这不就是动态代理么,通过动态代理对目标对象进行增强,增强的逻辑在Plugin#invoke方法(因为Plugin实现了InvocationHandler接口)

当 interfaces.length > 0 这个条件满足的时候才会进行代理,来追一下这个条件怎么才会满足?

org.apache.ibatis.plugin.Plugin#getSignatureMap
在这里插入图片描述
org.apache.ibatis.plugin.Plugin#getAllInterfaces
在这里插入图片描述
就是解析Signature注解获取要代理的类及其方法,如果当前的类需要进行代理,才会进行代理,否则不会进行代理

好了我们现在只需要看看InterceptorChain#pluginAll在哪些地方被调用了就能确定插件可以对哪些对象进行增强

在这里插入图片描述
可以看到Mybatis的四大重要组件Executor,StatementHandler,ParameterHandler,ResultSetHandler都可以被进行代理

当被代理的方法执行的时候会先执行Plugin#invoke方法(Plugin实现了InvocationHandler接口)
在这里插入图片描述
如果当前的方法需要被代理,则会调用Interceptor#intercept方法(这个方法里面我们写了增强逻辑),并把相关的参数封装为一个Invocation类
在这里插入图片描述
为了方便执行被代理的方法,Invocation提供了一个proceed方法。好了插件逻辑就分析完了!

参考博客

mybatis插件编写
[1]https://www.cnblogs.com/xrq730/p/6972268.html
mybatis插件实现原理
[2]https://www.cnblogs.com/xrq730/p/6984982.html
[3]https://juejin.im/post/5abe12f5f265da237411177f

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java识堂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值