【Mybtais】Mybatis 插件 Plugin开发(一)动态代理步步解析
需求:
对原有系统中的方法进行‘拦截’,在方法执行的前后添加新的处理逻辑。
分析:
不是办法的办法就是,对原有的每个方法进行修改,添加上新的逻辑;如果需要拦截的方法比较少,选择此方法到是会节省成本。但是面对成百上千的方法怎么办?此时需要用到动态代理来实现。
场景:
例如:对原有的系统添加日志记录、添加性能分析等等。。。
举例:
如下,需要对Sleep对象的sleep方法进行“拦截”,并在此方法的执行前后添加新的逻辑。想知道‘睡觉前干了什么?睡觉后干了什么?’
interface Sleep { public void sleep(); }
public class SleepImpl implements Sleep{ public void sleep() { System.out.println("我于"+new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date())+"开始睡觉"); } }
创建动态代理类,实现InvocationHandler接口即可。下面的wrap方法:传入要被代理的对象target。返回包装后的代理对象。$Proxy 打断点会看到这样的对象。针对下面的sleepProxy对象,sleepProxy.sleep()调用需要拦截的方法。实际上调用的是Plugin中的invoke方法。invoke方法中的method.invoke(target,args)是真是的调用被代理对象的sleep方法。所以直接在此语句的前后添加相应的逻辑即可满足需要。
public class Plugin implements InvocationHandler { private Object target; Plugin(Object target){ this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //睡觉前做的事 Object result = method.invoke(target, args); //睡觉后做的事 return result; } public static Object wrap(Object target){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new Plugin(target)); } }
public class Main { public static void main(String[] args) { //要被代理的对象 Sleep sleep = new SleepImpl(); //代理对象 Sleep sleepProxy = (Sleep)Plugin.wrap(sleep); sleepProxy.sleep(); } }
到此,你以为就结束了?不 ,这个仅仅是 说了在睡觉 前后做了什么事,加入还想知道,你在睡觉前后吃了什么东西?当然睡觉后吃东西有点说不通。但 意会就可以了。还有其他巴拉巴拉的需求。你该怎么做?是不是要把所有的 新的逻辑都方法 Plugin中invoke方法中去?这样不合适吧!乱 乱 乱 这样。那咱们能不能抽象出来一个拦截接口,接口中有拦截后要做什么的方法。各种需求只需要实现这个拦截接口即可!
interface Interceptor { public void interceptBefore()throws Exception; public void interceptAfter()throws Exception; }
public class SleepBeforeAndAfter implements Interceptor { public void interceptBefore() throws Exception { System.out.println("之前。。。"); } public void interceptAfter() throws Exception { System.out.println("之后。。。"); } }
然后动态代理类Plugin需要修改
/** * 动态代理 * * @author 魏正迪 * 2018年10月13日 */ public class Plugin implements InvocationHandler { private Object target; private List<Interceptor> iList = new ArrayList<Interceptor>(); Plugin(Object target , List<Interceptor> iList){ this.target = target; this.iList = iList; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { for(Interceptor i :iList){ i.interceptBefore(); } Object result = method.invoke(target, args); for(Interceptor i :iList){ i.interceptAfter(); } return result; } public static Object wrap(Object target,List<Interceptor> iList){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new Plugin(target,iList) ); } }
public class Main { public static void main(String[] args) { Sleep sleep = new SleepImpl(); List<Interceptor> iList = new ArrayList<Interceptor>(); iList.add(new SleepBeforeAndAfter()); Sleep sleepProxy = (Sleep)Plugin.wrap(sleep,iList); sleepProxy.sleep(); } }
现在想对每个对象的方法进行拦截,直接实现Interceptor接口即可!实现其中的两个方法。此时我们新加的逻辑和原有的逻辑并没有什么交集。假如我们想在interceptor中的两个方法中使用被代理对象的各种属性,此时该怎么做?首先想到是将interceptor接口的两个方法添加参数。
public class SleepBeforeAndAfter implements Interceptor { public void interceptBefore(Object target, Method method, Object[] args) throws Exception { System.out.println("之前。。。interceptBefore(Object target, Method method, Object[] args)"); } public void interceptAfter(Object target, Method method, Object[] args) throws Exception { System.out.println("之后。。。interceptAfter(Object target, Method method, Object[] args)"); } }
到此,个人感觉没啥问题了【大牛如发现明显不符的请指出】。但但但但是我们奔着简单明了、面向对象的思想(其实就是mybatis源码插件设计)。我们做出进一步的精简。于是Invocation对象产生了。看到Method对象传进来了。我们是不是可以想到,我们不再 在Plugin中的invoke方法中调用method.invoke(target,args);了,而是在Intercetpor中处理完前后逻辑后进行调用。这样分工明确了。
/** * 拦截对象的包装 * @author 魏正迪 * 2018年10月13日 */ public class Invocation { private Object target; private Object []args; private Method method; Invocation(Object target,Method method,Object[] args){ this.target = target; this.args = args; this.method = method; } /** * 执行拦截对象的对应的方法 * @return * @throws Exception */ public Object process() throws Exception{ return method.invoke(target, args); } }
此时拦截器Interceptor应该是这样的
interface Interceptor { public Object intercept(Invocation invocation)throws Exception; }
public class SleepBeforeAndAfter implements Interceptor { public Object intercept(Invocation invocation) throws Exception{ System.out.println("拦截sleep方法要执行的方法之前"); Object result = invocation.process(); System.out.println("拦截sleep方法要执行的方法之后"); return result; } }
此时Plugin应该是这样的
public class Plugin implements InvocationHandler { private Object target; private Interceptor interceptor; Plugin(Object target,Interceptor interceptor){ this.target = target; this.interceptor = interceptor; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Invocation invocation = new Invocation(target,method,args); return interceptor.intercept(invocation); } public static Object wrap(Object target,Interceptor interceptor){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new Plugin(target,interceptor) ); } }
public class Main { public static void main(String[] args) { Sleep sleep = new SleepImpl(); SleepBeforeAndAfter s = new SleepBeforeAndAfter(); Sleep sleepProxy1 = (Sleep)Plugin.wrap(sleep,s); sleepProxy1.sleep(); Sleep sleepProxy2 = (Sleep)Plugin.wrap(sleepProxy1, s); sleepProxy2.sleep(); } }
到此,mybatis插件开发的引言完毕!其实是使用了动态代理和责任链结合的方式。