在上一篇文章中我们已经全面的了解了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的实例即可。
也许以上很多可能表述有误,欢迎指正,最后谢谢你的阅读,你的支持是我写作的动力。