Mybatis插件实战进阶篇

在上一篇文章中我们已经全面的了解了Mybatis插件相关实现原理,以及它在实际开发应用中的作用,用的好的话,可以实现系统松耦合,动态扩展。这一篇废话不多说,我将带你领略插件在处理含嵌套方法场景的接口时实战编写指南,以及那些我踩过的坑。

要代理的接口
这里讲的是一种特殊场景,即要代理的接口有A、B两个方法,在实际业务流程上A方法会调用B方法。

要实现的特性
希望运用Mybatis插件机制来在A方法before切入一段预处理过程,after切入一段后置处理,B方法也是如此,如此一来为如下期望顺序:

beforeA
A
beforeB
B
afterB
afterA

太简了你就入坑了
小强同学拿到这份需求,立马说太简了,他于是写了两个InterceptorB,InterceptorA。
然后先用InterceptorB去拦截B方法产生代理targetB,然后两用 InterceptorA去plugin
targetB产生targetA,
最终调用targetA.A()
结果输出如下:

beforeA
A
B
afterA

此时你或许已傻眼

带你出坑
上述问题出在targetA调用A方法时,调用的确实是代理对象的方法,但是在准备调用B方法时,此时栈帧进入的是原对象的A方法中,因为控制调用B方法的时机是由原对象的方法中去控制的,原对象的A方法调用的B方法必然不是代理对象的B方法,所以会出现以上问题,这也是因为Mybatis是基于JDK动态接口代理的原因,其本质是代理对象,这并不像JAVA本身的类方法重写机制,可以根据对象实例的不同,方法运行能够动态绑定。

制定策略
综上我们发现问题根本原因出在:如果被调用方法只要嵌套在本类方法中,我们是永远不可能达到以上的要求,因为我们在A方法进去的时候,永远调不到代理的B方法,所以我们要改变这个关键的现状。
具体策略如下:
首先还是InterceptorB拦截B方法,然后再是InterceptorA去重写*A方法,注意这里我用到的是重写*两字,重写的时候要注意A方法的原来逻辑大体不变,而在调用B方法的时候,我们只需要通用invocation对象拿到上面InterceptorB代理出来的targetB对象,这个时候cast为我们代理的接口类型,这个时候再调用B方法,此时调用的B方法才是我们已经拦截好的B方法,最后在重写的这个代码块之前之后加上我们对A拦截的相关逻辑,最终就实现了以上需求。

实战代码演示
笔者以上所讲都是用比较通俗易懂的话娓娓道来,也许不够词汇不够专业,但是我相信你如果认真看,一定能明白其中的诀窍。而以上的剖析也绝对是实战之下的感受。在此贴出我已经码出的Demo(比较简单,我就不打扰大家看了,敬请品赏):
People

package org.apache.ibatis.plugin.test.nest;

public interface People {

    void work();

    void programme();
}

Programmer

package org.apache.ibatis.plugin.test.nest;

public class Programmer implements People {
    @Override
    public void work() {

        System.out.println("8点40准时开站会!");

        programme();


    }

    @Override
    public void programme() {
        System.out.println("编程中");
    }
}

ProgrammeInterceptor

package org.apache.ibatis.plugin.test.nest;

import org.apache.ibatis.plugin.*;

import java.util.Properties;

@Intercepts({
        @Signature(type = People.class, method = "programme", args = {})
})
public class ProgrammeInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("编程之前我得好好理清需求");
        invocation.proceed();
        System.out.println("编程之后我得执行单元测试并部署到测试环境进行验证");
        return null;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

WorkInterceptor

package org.apache.ibatis.plugin.test.nest;

import org.apache.ibatis.plugin.*;

import java.util.Properties;

@Intercepts({
        @Signature(type = People.class, method = "work", args = {})
})
public class WorkInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        System.out.println("坐巴士去上班");

        System.out.println("8点40准时开站会!");
        People programmer = (People) invocation.getTarget();
        programmer.programme();

        System.out.println("坐巴士回家");

        return null;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

NestTest

package org.apache.ibatis.plugin.test.nest;

import org.apache.ibatis.plugin.InterceptorChain;

public class NestTest {

    public static void main(String[] args) {
        InterceptorChain interceptorChain = new InterceptorChain();
        interceptorChain.addInterceptor(new ProgrammeInterceptor());
        interceptorChain.addInterceptor(new WorkInterceptor());

        People target = new Programmer();
        People wrappedProgrammer = (People) interceptorChain.pluginAll(target);
        wrappedProgrammer.work();
    }

}

最后注意点
笔者在实战时,其实并不是最终测试直接是期望效果,最终剖析发现,我在拦截器的注解@Signature上申明type时,type并不是接口,导致了最终运行有问题,这就要提到JDK动态代理,实质是只支持接口的动态代理,并不支持非接口类,而代理出来的对象的实现类是在运行时由底层技术实现的,它在并非是(如上)Programmer的子类,而是People的子类,也就是说People是接口,它是代理实现类与Programmer类的父类,而代理实现类与Programmer类并没有直接交集,它们只是在切入代理的实现上有交互而已,同时我们发现我们要代理Programmer类的方法A和B,那么这些要代理的方法A和B也必须抽取到上一层的接口People中,而用拦截器去真正代理People接口,实际运行传入Programmer的实例即可。

也许以上很多可能表述有误,欢迎指正,最后谢谢你的阅读,你的支持是我写作的动力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值