前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis学习笔记(三)- Mybatis插件原理

Mybatis学习笔记(三)- Mybatis插件原理

作者头像
写一点笔记
发布2020-12-31 10:27:14
3290
发布2020-12-31 10:27:14
举报
文章被收录于专栏:程序员备忘录程序员备忘录

通过之前的分析和代码跟踪,我们基本上了解了mybatis的一些大概情况。但在文中结尾的时候,我们说对于mybatis的插件的原理还不足够的清晰。当时我们通过分析得出的结论是pagehelper插件的starter会将拦截器放到ioc容器中,然后在sqlsessionfactory创建的时候会将拦截器设置到executor中。但是我们executor的显示调用上我们并没有发现走了拦截器相关的逻辑。而我们知道拦截器是一个jdk代理,所以我们将重点放到jdk代理上。希望据此能够学到jdk代理的内涵。

首先我们实现一个简单的jdk代理。

代码语言:javascript
复制
public interface MybatisPluginTest {

    void execute();
}
代码语言:javascript
复制
public class MybatisPluginTestImpl implements MybatisPluginTest{
    @Override
    public void execute() {
        System.out.println("---------执行中------------");
    }
}
代码语言:javascript
复制
public class MybatisPluginsProxy implements InvocationHandler {

    private Object object;

    MybatisPluginsProxy(Object o){
        this.object=o;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("-----执行前");
        Object object1=method.invoke(object,args);
        System.out.println("-----执行后");
        return object1;
    }

    public static Object wrap(Object target){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),new MybatisPluginsProxy(target));
    }

}
代码语言:javascript
复制
public class MybatisPluginMain {


    public static void main(String[] args) {
        MybatisPluginTest test=new MybatisPluginTestImpl();
        MybatisPluginTest proxy= (MybatisPluginTest) MybatisPluginsProxy.wrap(test);
        proxy.execute();
    }
}

执行的结果

通过jdk代理我们在代理类中做了一些操作,但是现实的状况是我们的业务可能会比较多,难道每次都都要改代码,这样好麻烦,而且扩容性并不好。但是我们的业务扩展其实都是可以通过接口进行抽象。我们只需要将接口的实现添加进来,然后代理就好,也就是说我们要代理的不在是一个,而是一批符合规范的所有。

据此,我们继续开始改进

代码语言:javascript
复制
public interface MyPlugin {

    void interceptor();
}
代码语言:javascript
复制

public class MyPluginOne implements MyPlugin {
    @Override
    public void interceptor() {
        System.out.println("--插件1");
    }
}
代码语言:javascript
复制
public class MyPluginTwo implements MyPlugin {
    @Override
    public void interceptor() {
        System.out.println("--插件2");
    }
}
代码语言:javascript
复制
public class MybatisPluginsProxy implements InvocationHandler {

    private Object object;

    private Listinterceptors;

    MybatisPluginsProxy(Object o,Listo1){
        this.object=o;
        this.interceptors=o1;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("-----执行前");
//先执行插件
        interceptors.forEach(MyPlugin::interceptor);
        Object object1=method.invoke(object,args);
        System.out.println("-----执行后");
        return object1;
    }

    public static Object wrap(Object o,Listtarget){
        return Proxy.newProxyInstance(o.getClass().getClassLoader(),
                o.getClass().getInterfaces(),new MybatisPluginsProxy(o,target));
    }

}
代码语言:javascript
复制
public class MybatisPluginMain {


    public static void main(String[] args) {
        MybatisPluginTest test=new MybatisPluginTestImpl();
 
       //添加两个插件
        MyPlugin test1=new MyPluginOne();
        MyPlugin test2=new MyPluginOne();
        List list=new ArrayList<>();
        list.add(test1);
        list.add(test2);
        MybatisPluginTest proxy= (MybatisPluginTest) MybatisPluginsProxy.wrap(test,list);
        proxy.execute();
    }
}

但是我们发现问题是我们的拦截器还不够成熟,我们的拦截器只能在要调用的方法之前或者之后执行。那么如何让插件真正的像个代理类一样工作。既然如此,那么我们就可以偷梁换柱一下,将我们的代理类放到拦截器里,然后执行方法的时候先走拦截器,然后在拦截器中调用真正的代理类方法。

1.代理包装接口

代码语言:javascript
复制
public interface MyInvocation {

    Object Testinterceptor() throws InvocationTargetException, IllegalAccessException;
}

2.代理包装实现

代码语言:javascript
复制
public class MyInvocationTest implements MyInvocation{

    private Object object;

    private Object[] plugin;

    private Method method;

    MyInvocationTest(Object o, Method method, Object[] myPlugin){
        this.object=o;
        this.plugin=myPlugin;
        this.method=method;
    }

    @Override
    public Object Testinterceptor() throws InvocationTargetException, IllegalAccessException {
//真正调用的代理
        return method.invoke(object,plugin);
    }
}

3.定义插件

代码语言:javascript
复制
public class MyPluginOne implements MyPlugin {
    @Override
    public Object interceptor(MyInvocation myInvocation) throws InvocationTargetException, IllegalAccessException {
        System.out.println("--插件1");
   
       Object o= myInvocation.Testinterceptor();
        System.out.println("插件1结束");
        return o;
    }


}

4.封装插件

代码语言:javascript
复制
public class MybatisPluginsProxy implements InvocationHandler {

    private Object object;

    private MyPlugin interceptors;

    MybatisPluginsProxy(Object o,MyPlugin o1){
        this.object=o;
        this.interceptors=o1;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("-----执行前");
        MyInvocation myInvocation=new MyInvocationTest(object,method,args);
        return interceptors.interceptor(myInvocation);
//        Object object1=method.invoke(object,args);
//        System.out.println("-----执行后");
//        return object1;
    }

    public static Object wrap(Object o,MyPlugin target){
        return Proxy.newProxyInstance(o.getClass().getClassLoader(),
                o.getClass().getInterfaces(),new MybatisPluginsProxy(o,target));
    }

}

5.代码执行

代码语言:javascript
复制
public class MybatisPluginMain {


    public static void main(String[] args) {
        MybatisPluginTest test=new MybatisPluginTestImpl();
        MyPlugin test1=new MyPluginOne();
        MyPlugin test2=new MyPluginOne();
        List list=new ArrayList<>();
        list.add(test1);
        list.add(test2);
        MybatisPluginTest proxy= (MybatisPluginTest) MybatisPluginsProxy.wrap(test,test2);
        proxy.execute();
    }
}

执行的结果:

通过上述改造,我们的插件就可以在真正代理类之外执行了。但是如果我们有多个插件,那么我们怎么做?我们观察到代理其实只是对最初的对象的代理。所以拦截器的方法和真正代理类的方法没有任何关系。据此拦截器也是一样的道理,如果我们对拦截器进行代理,如果有多个拦截器那么就对拦截器的代理再进行代理。这样我们的真正要执行的代理类就被影藏到最深处,这样就实现了我们的目标。这里我们要明确的是我们的真正代理类因为要被影藏的最深所以要最早定义,因为我们要对拦截器进行代理,所以我们的拦截器要新增代理的功能。

改造之后的代码如下:

代码语言:javascript
复制
public interface MyPlugin {

    Object interceptor(MyInvocation myInvocation) throws InvocationTargetException, IllegalAccessException;

    //插件
    Object plugin(Object o);
}
代码语言:javascript
复制
public class MyPluginOne implements MyPlugin {
    @Override
    public Object interceptor(MyInvocation myInvocation) throws InvocationTargetException, IllegalAccessException {
        System.out.println("--插件1");

       Object o= myInvocation.Testinterceptor();
        System.out.println("插件1结束");
        return o;
    }

    @Override
    public Object plugin(Object o) {
        //将当前插件整合到上层代理中,并返回整合之后的代理类
        return MybatisPluginsProxy.wrap(o,this);
    }
}
代码语言:javascript
复制
public class MybatisPluginMain {
    public static void main(String[] args) {
        MybatisPluginTest test=new MybatisPluginTestImpl();
        MyPlugin test1=new MyPluginOne();
        MyPlugin test2=new MyPluginTwo();
        test= (MybatisPluginTest) test1.plugin(test);
        test= (MybatisPluginTest) test2.plugin(test);
        test.execute();
    }
}

代码改造到这里,我们基本已经完成了所需要的功能。但是我们发现在添加插件这件事情上,我们可以提取公因式。于是就有了另类的改造。

代码语言:javascript
复制
public class InterceptorChain {

    private Listinterceptor=new ArrayList<>(10);


    public Object pluginAll(Object o){
        for (int i = 0; i<interceptor.size() ; i++) {
           o= interceptor.get(i).plugin(o);
        }
        return o;
    }

    public void addInterceptor(MyPlugin interceptor){
        this.interceptor.add(interceptor);
    }
}
代码语言:javascript
复制

public class MybatisPluginMain {
    public static void main(String[] args) {
        MybatisPluginTest test=new MybatisPluginTestImpl();
        MyPlugin test1=new MyPluginOne();
        MyPlugin test2=new MyPluginTwo();
                   InterceptorChain interceptorChain=new InterceptorChain();
        interceptorChain.addInterceptor(test1);
        interceptorChain.addInterceptor(test2);
        test= (MybatisPluginTest) interceptorChain.pluginAll(test);
        test.execute();
    }
}

至此我们大概学习了mybatis插件的核心原理。那么我就去看看mybatis插件的代码。我们继续找到我们代码执行sql的地方

如图所示,在executor中创建了sql处理器。

代码语言:javascript
复制
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//使用责任链模式创建代理
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

同样的,mybatis的四大处理器都有安装插件的功能。也就是说在在启动的时候executor的所有插件都已经设置进去。当我们调用executor的方法的时候,其实就已经开始插件的执行。

需要注意的是这里的的拦截器的递归调用的最后就是我们最原始的代理类simpleExecutor或者cacheExecutor。

这里可能有人要问我们分页插件的拦截器看样子会在sql处理前执行,那么他这个sql是怎么做的。其实这样想就已经变成了刻舟求剑,我们的代码自从有了代理,他就仅仅由代理调用,而我们将插件和代理整合不断的嵌套。所以我们只需要保证我们最后执行的插件调用了真正代理类的相应方法即可。如图所示就是mybatis的查库操作。

而至于真正代理类所要执行的方法之后的其他代理那就一个新的代理插件嵌套的过程。所以说执行的流程还是相当的复杂,所以我们还是少写无用的插件的最好。不过这种插件的设计是非常值得我们学习的。

参考文献:

https://www.cnblogs.com/qdhxhz/p/11390778.html

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员备忘录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档