前言
Spring AOP 使用了两种代理机制:一种是基于 JDK 的动态代理;另一种是基于 CGLib 的动态代理。为了彻底搞懂 Spring AOP 的实现原理,我阅读了这两种代理模式的源码,本篇文章将通过一个非常简单的案例来分析 JDK 动态代理的实现。
案例
创建一个计算器类进行加法操作,在操作前打印日志
工程环境
JDK: 1.8
maven 3.6.1
IDEA: 2020.3.2
编写被代理类
因为 jdk 只提供针对接口的代理,首先需要创建 Calculator 接口,由于这里只是简单地测试,不再引入复杂的业务逻辑,Calculator 接口里只有一个 add 方法,对传入的两个 int 参数进行加法计算,并返回 int 类型的和。
// package com.xzy.jdkpublic interface Calculator { int add(int i, int j);}
复制代码
MyCalculator 作为实现类,实现了 add 方法。
// package com.xzy.jdkpublic class MyCalculator implements Calculator { @Override public int add(int i, int j) { int result = i + j; return result; }}
复制代码
编写创建代理的类
Calculate 的静态方法 getProxy,根据传入的需要被代理的 Calculator 实例,创建其代理对象,在执行 add 方法前,打印日志。
// package com.xzy.jdkpublic class CalculatorProxy { public static Calculator getProxy(Calculator calculator) { ClassLoader loader = calculator.getClass().getClassLoader(); Class<?>[] interfaces = calculator.getClass().getInterfaces(); InvocationHandler h = (proxy, method, args) -> { Object result = null; if ("add".equals(method.getName())) { System.out.println("执行add方法"); } result = method.invoke(calculator, args); return result; }; Object proxy = Proxy.newProxyInstance(loader, interfaces, h); return (Calculator) proxy; }
复制代码
编写测试类
将系统属性 sun.misc.ProxyGenerator.saveGeneratedFiles 设置为 true,会将生成的动态代理类保存到本地。
// com.xzy.jdk.Testpublic class Test { public static void main(String[] args) { //将生成的代理类保存到本地 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); Calculator proxy = CalculatorProxy.getProxy(new MyCalculator()); int result = proxy.add(1, 1); System.out.println("result = " + result); System.out.println(proxy.getClass()); }}
复制代码
运行结果
执行add方法result = 2com.sun.proxy.$Proxy0
复制代码
原理分析
在运行测试类时,由于我们设置了 sun.misc.ProxyGenerator.saveGeneratedFiles 属性,因此会将 JDK 动态代理生成的实现类 $Proxy0,保存到本地。
注:考虑篇幅问题,这里仅展示 add 方法。
// package com.sun.proxy;public final class $Proxy0 extends Proxy implements Calculator { private static Method m1; private static Method m2; private static Method m3; private static Method m0;
public $Proxy0(InvocationHandler var1) throws { super(var1); }
public final int add(int var1, int var2) throws { try { return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2}); } catch (RuntimeException | Error var4) { throw var4; } catch (Throwable var5) { throw new UndeclaredThrowableException(var5); } }
static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("com.xzy.jdk.Calculator").getMethod("add", Integer.TYPE, Integer.TYPE); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } }}
复制代码
在运行结果中打印了代理类的类名称 com.sun.proxy.$Proxy0,因此测试类 Test 中的 proxy 为生成的代理类 $Proxy0 的实例。
下面我们继续看一下生成的代理类 $Proxy0 的源码,这个代理类中有四个 Method 类型的类变量,在最下面的静态代码块中,通过反射的方式对这四个 Method 类型的类变量分别进行了初始化,他们分别对应 Object 的 equals、toString、hashCode 以及 Calculator 的 add 方法。
$Proxy0 类中只有一个构造函数,需传入一个类型为 java.lang.reflect.InvocationHandler 的参数,在实例化
$Proxy0 时,传入的实例化参数其实是在 CalculatorProxy 的 getProxy 方法中创建的 InvocationHandler 实例。
在代理 $Proxy0 的 add 方法中将执行 InvocationHandler 的 invoke 方法,执行该方法时,将代理实例、代表 Calculator 的 add 方法的 Method 实例,以及参数传递过去,在 invoke 方法中,打印日志后通过反射执行被代理实例的 add 方法。执行的过程如下图:
可以通过代码来验证这个过程,修改 MyCalculator 的 add 方法,增加一行代码来打印方法执行的堆栈。
public int add(int i, int j) { new Exception().printStackTrace(); int result = i + j; return result; }
复制代码
执行测试类,结果如下:
执行add方法result = 2java.lang.Exception at com.xzy.jdk.MyCalculator.add(MyCalculator.java:6) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at com.xzy.jdk.CalculatorProxy.lambda$getProxy$0(CalculatorProxy.java:17) at com.sun.proxy.$Proxy0.add(Unknown Source) at com.xzy.jdk.Test.main(Test.java:9)
复制代码
在打印的结果中,第 9 行是执行 CalculatorProxy 中创建的 InvocationHandler 的方法。由打印的堆栈可以验证我们上面推断的执行过程的正确性。
源码执行流程
调用 Proxy.newProxyInstance(loader, interfaces, h)方法。
调用 Proxy 的静态方法,会触发 Proxy 这个类的初始化,在 Proxy 初始化过程中,最重要的操作是对静态变量 proxyClassCache 进行初始化,代码附在下面,在构造函数中传入了两个非常重要的参数 KeyFactory 和 ProxyClassFactory,一定要记住这两个非常有用的参数。
proxyClassCache 负载缓存生成代理类实例,从该 cache 中获取代理 Class 对象。
如果 cache 中没有,创建 java.lang.reflect.WeakCache.Factory 对象,通过该对象来创建代理 Class。
java.lang.reflect.WeakCache.Factory 调用 proxyClassCache 中的 ProxyClassFactory 对象进行代理 Class 创建。
此时代理类 $Proxy0 已经存在 JVM 中,把 CalculatorProxy 中的 InvocationHandler 实例作为参数,调用 $Proxy0 的构造函数 $Proxy0(InvocationHandler var1) 创建代理对象。
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
复制代码
总结
JDK 动态代理只能对接口进行代理,对被代理类的方法通过反射的方式调用。而下篇文章将会分析 cglib 的原理,cglib 可以对类进行代理,cglib 引入了 FastClass 机制,可以直接调用被代理类对象的方法,性能比 JDK 的动态代理高。
源码地址:https://github.com/xuziyang/proxy-demo
评论