前言
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.jdk
public interface Calculator {
int add(int i, int j);
}
复制代码
MyCalculator 作为实现类,实现了 add 方法。
// package com.xzy.jdk
public 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.jdk
public 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.Test
public 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 = 2
com.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 = 2
java.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
评论