一、前言
最近项目上,要做一个日志审计的功能(通用SDK,期望可以在任何项目中使用),需要对Mybatis层所有的DDL操作做拦截,以将微服务的链路ID、执行人、Controller门面方法全部持久化到业务库。借此机会深入研究了一下MyBatis的源码,该篇文章重点在于推导从JDK动态代理如何一步步演变到MyBatis Plugin的实现。
二、JDK动态代理
概述
JDK动态代理:在程序运行时,利用反射机制 获取被代理类的字节码内容 进而生成一个实现代理接口的匿名类(代理类),在调用具体方法前调用InvokeHandler
来处理;
-
JDK Proxy动态代理的API包在java.lang.reflect下,其核心api包含InvocationHandler(Interface)和Proxy(class)。
-
Proxy是Java动态代理机制的主类,其提供一组静态方法来为一组接口动态地生成代理类及其对象。
static InvocationHandler getInvocationHandler(Object proxy) --> 用于获取指定代理对象所关联的调用处理器。
static Class getProxyClass(ClassLoader loader, Class[] interfaces) --> 用于获取关联于指定类装载器和一组接口的动态代理类的类对象。
static boolean isProxyClass(Class cl) --> 用于判断指定类对象是否具有一个动态代理类
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) --> 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例。
h:表示当这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。
-
InvocationHandler是负责连接代理类和委托类的中间类必须要实现的接口。
它定义了一个
invoke()
方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。 -
优点:
- 可以方便的对地代理类的函数进行统一的处理,而不用修改这个代理类的函数。
- 简化了编程工作,提高了软件系统的扩展性。因为Java反射机制可以生成任意类型的动态代理类。
- 动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
- 接口增加一个方法,动态代理类会直接自动成成对应的代理方法。
缺点:
- JDK Proxy只能对实现了接口的类才能代理,没有接口实现的类,JDK Proxy是无法代理的。
实现方式:
-
委托类:JDK动态代理中
接口
是必须的,要求委托类必须实现某个接口。 -
使用动态代理时,需要定义一个位于代理类与委托类之间的中介类。
-
这个
中介类必须实现InvocationHandler接口
;作为调用处理器"拦截"对代理类方法的调动。InvocationHandler
接口定义如下:当我们调用代理类对象的方法时,这个"调用"会转送到invoke()方法中,代理类对象作为proxy参数传入;
参数method标识了我们具体调用的是代理类的哪个方法;
args为这个方法的参数。
javapackage java.lang.reflect; public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
中介类:
我们对代理类中的所有方法的调动都会变为对invoke()的调用,我们可以在invoke()方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。
中介类通过聚合方式持有委托类对象引用,把外部对invoke的调用最终都转为对委托类对象的调用。
javapublic class MyDynamicProxy implements InvocationHandler { /** * obj为委托类对象 */ private Object obj; public MyDynamicProxy(Object obj){ this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before"); Object result = method.invoke(obj, args); System.out.println("after"); return result; } }
代理类通过Proxy.newProxyInstance()方法生成;
总结一下:中介类与委托类构成了静态代理关系,在这个关系中,中介类是代理类,委托类就是委托类;代理类与中介类也构成一个静态代理关系。
-
原理
动态代理关系由两组静态代理关系组成,这就是动态代理的原理;
实际上,中间类(Proxy)与委托类构成了静态代理关系,在这个关系中,中间类是代理类,委托类是委托类。
然后代理类与中间类也构成一个静态代理关系,在这个关系中,中间类是委托类,代理类是代理类。
通过newProxyInstance()方法获取到代理类实例,然后通过这个代理类实例调用代理类的方法,对代理类的方法的调用实际上都会调用中介类(调用处理器)的invoke()方法,在invoke方法中我们调用委托类的相应方法,并且可以添加自己的处理逻辑。
动态代理
-
委托类(需要被代理的类)Target
-
Target :
javapublic interface Target{ String execute(String name); }
-
委托类(需要被代理的类)Target
javapublic class TargetImpl implements Target { @Override public String execute(String name) { System.out.println("targetImpl execute() " + name); return name; } }
-
中介类TargetProxy
java/** * JDK动态代理:中介类 */ public class TargetProxy implements InvocationHandler { /** * target为委托类对象 */ private Object target; public TargetProxy(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before intercept......."); Object result = method.invoke(target, args); System.out.println("after intercept......."); return result; } /** * 创建代理对象 * * @param target 委托类 * @return */ public static Object wrap(Object target) { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxy(target)); } }
wrap(Object target)
方法用于创建代理对象。 -
测试类(运行结果)
javapublic class MainTest { public static void main(String[] args) { // 委托类 Target target = new TargetImpl(); // 代理类 Target targetProxy = (Target) TargetProxy.wrap(target); // 实际调用的是代理对象的invoke()方法 targetProxy.execute("Hello World!"); } }
运行结果:
javabefore intercept....... targetImpl execute() Hello World! after intercept.......
结果分析:
- 使用JDK动态代理可以解决问题,实现前置、后置拦截。不过我们要拦截处理的逻辑全部耦合在invoke()方法中,不符合面向对象的思想;想成为一个架构师,必须要抽象起来呀;
- 尝试设计一个Interceptor接口,需要做什么拦截处理实现接口即可。
-
抽象出拦截逻辑(增加Interceptor接口)
java/** * 抽象拦截器 * * @author Saint */ public interface Interceptor { /** * 具体的拦截处理 */ void intercept(); }
Interceptor实现:
日志拦截器
javapublic class LogInterceptor implements Interceptor { @Override public void intercept() { System.out.println("start record log...."); } }
事务拦截器
javapublic class TransactionInterceptor implements Interceptor { @Override public void intercept() { System.out.println("start transaction..."); } }
重写代理类TargetProxy2
增加拦截器处理逻辑;在创建代理类TargetProxy2时添加所有的拦截器;
javapublic class TargetProxy2 implements InvocationHandler { /** * target为委托类对象 */ private Object target; /** * 拦截器集合 */ private List<Interceptor> interceptorList = new ArrayList<>(); public TargetProxy2(Object target, List<Interceptor> interceptorList) { this.target = target; this.interceptorList = interceptorList; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /** * 拦截处理 */ interceptorList.forEach(Interceptor::intercept); Object result = method.invoke(target, args); return result; } /** * 创建代理对象 * * @param target 委托类 * @return */ public static Object wrap(Object target, List<Interceptor> interceptorList) { TargetProxy2 targetProxy = new TargetProxy2(target, interceptorList); return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), targetProxy); } }
测试类(运行结果)
javapublic class MainTest2 { public static void main(String[] args) { // 委托类 Target target = new TargetImpl(); // 所有的拦截器 List<Interceptor> interceptorList = new ArrayList<>(); interceptorList.add(new LogInterceptor()); interceptorList.add(new TransactionInterceptor()); // 代理类 Target targetProxy = (Target) TargetProxy2.wrap(target, interceptorList); // 实际调用的是代理对象的invoke()方法 targetProxy.execute("Hello World!"); } }
运行结果:
javastart record log.... start transaction... targetImpl execute() Hello World!
结果分析:
现在可以实现动态添加拦截器了,也不需要每次都去修改中间类TargetProxy了;
不过我们这里只有前置拦截耶,聪明的你肯定发现了,我可以定义两个在Interceptor接口里定义两个方法啊,一个beforeIntercept()、一个afterIntercept();
但是这样拦截器无法知道拦截对象的信息;我们需要做进一步的抽象,将拦截对象的信息封装起来,作为拦截器拦截方法的入参;目标对象的真正执行交给拦截器interceptor来完成,这样我们不仅可以实现前后置拦截,还能对拦截对象的参数做一些修改。参考MyBatis源码,我们设计一个Invocation对象。
-
抽象逻辑器拦截方法入参
拦截方法入参:Invocation
java/** * 拦截器拦截方法入参 */ public class Invocation { /** * 目标对象(委托类) */ private Object target; /** * 执行的目标方法 */ private Method method; /** * 执行方法的参数 */ private Object[] args; public Invocation(Object target, Method method, Object[] args) { this.target = target; this.method = method; this.args = args; } /** * 执行目标对象的方法 * * @return * @throws Exception */ public Object process() throws Exception { return method.invoke(target, args); } }
修改拦截接口(Interceptor2)
javapublic interface Interceptor2 { /** * 具体的拦截处理 */ Object intercept(Invocation invocation) throws Exception; }
事务拦截器实现:
javapublic class TransactionInterceptor2 implements Interceptor2 { @Override public Object intercept(Invocation invocation) throws Exception { System.out.println("start transaction..."); Object result = invocation.process(); System.out.println("end transaction..."); return result; } }
中介(中间)类TargetProxy3
javapublic class TargetProxy3 implements InvocationHandler { /** * target为委托类对象 */ private Object target; /** * 拦截器集合 */ private Interceptor2 interceptor2; public TargetProxy3(Object target, Interceptor2 interceptor2) { this.target = target; this.interceptor2 = interceptor2; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Invocation invocation = new Invocation(target, method, args); return interceptor2.intercept(invocation); } /** * 创建代理对象 * * @param target 委托类 * @return */ public static Object wrap(Object target, Interceptor2 interceptor2) { TargetProxy3 targetProxy = new TargetProxy3(target, interceptor2); return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), targetProxy); } }
测试类(运行结果)
javapublic class MainTest3 { public static void main(String[] args) { // 委托类 Target target = new TargetImpl(); // 所有的拦截器 Interceptor2 transactionInterceptor = new TransactionInterceptor2(); // 代理类 Target targetProxy = (Target) TargetProxy3.wrap(target, transactionInterceptor); // 实际调用的是代理对象的invoke()方法 targetProxy.execute("Hello World!"); } }
运行结果:
javastart transaction... targetImpl execute()Hello World! end transaction...
结果分析:
-
此时拦截器已经可以获取到拦截对象的信息;不过这样仿佛又回到了最初的起点,只是将原本写在代理类TargetProxy中的逻辑移到了Interceptor,并且此时拦截器也只有一个;
-
上面这种做法看着就比较别扭,我们换种方式理解:对于目标类(委托类)而言,它只需要知道它内部被插入了哪些拦截器;
-
换言之,我们可以把拦截器看做是委托类和代理类的中间类(TargetProxy)的代理类,即中间类执行方法的动作由拦截器负责完成;
-
针对多个拦截器而言,也可以兼容,实际就是代理嵌套再代理。
拦截器里插入目标对象
重写拦截器
javapublic interface Interceptor3 { /** * 具体的拦截处理 */ Object intercept(Invocation invocation) throws Exception; /** * 插入目标类(即拦截器作用于那个委托类) */ Object plugin(Object target); }
事务拦截器:
javapublic class TransactionInterceptor3 implements Interceptor3 { @Override public Object intercept(Invocation invocation) throws Exception { System.out.println("start transaction..."); Object result = invocation.process(); System.out.println("end transaction..."); return result; } @Override public Object plugin(Object target) { return TargetProxy4.wrap(target, this); } }
日志拦截器:
javapublic class LogInterceptor2 implements Interceptor3 { @Override public Object intercept(Invocation invocation) throws Exception { System.out.println("start record log..."); Object result = invocation.process(); System.out.println("end record log..."); return result; } @Override public Object plugin(Object target) { return TargetProxy4.wrap(target, this); } }
中间类(TargetProxy4)和3>一样
javapublic class TargetProxy4 implements InvocationHandler { /** * target为委托类对象 */ private Object target; /** * 拦截器集合 */ private Interceptor3 interceptor3; public TargetProxy4(Object target, Interceptor3 interceptor3) { this.target = target; this.interceptor3 = interceptor3; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Invocation invocation = new Invocation(target, method, args); return interceptor3.intercept(invocation); } /** * 创建代理对象 * * @param target 委托类 * @return */ public static Object wrap(Object target, Interceptor3 interceptor3) { TargetProxy4 targetProxy = new TargetProxy4(target, interceptor3); return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), targetProxy); } }
测试类(执行结果)
javapublic class MainTest4 { public static void main(String[] args) { // 委托类 Target target = new TargetImpl(); // 所有的拦截器 Interceptor3 transactionInterceptor = new TransactionInterceptor3(); Interceptor3 logInterceptor = new LogInterceptor2(); // 由于log拦截器在最后,所以log拦截器先执行 target = (Target) transactionInterceptor.plugin(target); target = (Target) logInterceptor.plugin(target); // 实际调用的是代理对象的invoke()方法 target.execute("Hello World!"); } }
执行结果:
javastart record log... start transaction... targetImpl execute() Hello World! start transaction... end record log...
结果分析:
- 最后插入的拦截器先执行;
- 本质上来讲这就是代理嵌套再代理;
时序图如下:
不过添加拦截器的方式我们可以再优化一下,我们再利用一下OOP的思想,搞个拦截器链;
-
-
拦截器链
再利用责任链模式,将拦截器串起来,搞成一个链;
拦截器链(InterceptorChain)
javapublic class InterceptorChain { private List<Interceptor3> interceptorList = new ArrayList<>(); /** * 插入所有拦截器 * * @param target * @return */ public Object pluginAll(Object target) { for (Interceptor3 interceptor : interceptorList) { target = interceptor.plugin(target); } return target; } public void addInterceptor(Interceptor3 interceptor) { interceptorList.add(interceptor); } /** * 返回一个不可修改集合,只能通过addInterceptor方法添加 * * @return */ public List<Interceptor3> getInterceptorList() { return Collections.unmodifiableList(interceptorList); } }
这里通过pluginAll()方法包一层,将所有的拦截器插入到目标类中去;
测试类(执行结果)
javapublic class MainTest5 { public static void main(String[] args) { // 委托类 Target target = new TargetImpl(); // 所有的拦截器 Interceptor3 transactionInterceptor = new TransactionInterceptor3(); Interceptor3 logInterceptor = new LogInterceptor2(); InterceptorChain interceptorChain = new InterceptorChain(); interceptorChain.addInterceptor(transactionInterceptor); interceptorChain.addInterceptor(logInterceptor); // 由于log拦截器在最后,所以log拦截器先执行 target = (Target) interceptorChain.pluginAll(target); // 实际调用的是代理对象的invoke()方法 target.execute("Hello World!"); } }
执行结果:
javastart record log... start transaction... targetImpl execute()Hello World! end transaction... end record log...
结果分析:
- 不分析了,到这也就结束了。
三、MyBatis Plugin插件
在MyBatis Plugin源码中我们可以看到,基本和我们上面推导的最后一种方式一样,其中Plugin相当于我们的TargetProxy。