代理模式可以说是应用最为广泛的设计模式之一,同时也是其他一些设计模式的基础或组成部分。
在上篇文章 深入浅出 Retrofit 中,就是通过 动态代理 来实现具体的网络请求逻辑。本着刨根究底的原则,这篇文章来探究一下动态代理的技术原理。
在这之前,先来看一下基础的 静态代理 。
接口类 ICount
和实现类 Counter
:
public interface ICount {
void test1();
void test2();
}
public class Counter implements ICount {
@Override
public void test1() {
System.out.println("test1");
}
@Override
public void test2() {
System.out.println("test2");
}
}
现在要加一个需求,统计 Counter
类中每个方法的耗时。本着开闭原则,对扩展开放,对修改关闭,最好不要去动 Counter 原本的逻辑,可以提供一个同样实现 ICount
的代理类 CounterProxy
:
public class CounterProxy implements ICount {
private Counter counter;
public CounterProxy(Counter counter) {
this.counter = counter;
}
@Override
public void test1() {
long start = System.currentTimeMillis();
counter.test1();
long end = System.currentTimeMillis();
System.out.println("test1" + " consume: " + (end - start));
}
@Override
public void test2() {
long start = System.currentTimeMillis();
counter.test2();
long end = System.currentTimeMillis();
System.out.println("test2" + " consume: " + (end - start));
}
}
这就是 静态代理 的基本使用。
看起来很简单,但是应用场景也很明显。在需要对接口方法做统一处理的场景下,典型的如 Retrofit,静态代理就是个灾难,你得为每一个接口方法都提供实现。
有没有办法把这个过程自动化呢?这就是 动态代理 的功能:自动生成代理类。
自动生成 Class 文件的方案很多,生写硬怼,Javassist,ASM 都可以。那么,是不是照着静态代理类的结构,在运行时自动生成就可以了呢?
这样设计显然是不合理的,为每个接口方法动态生成实现逻辑,那每个方法单独的处理逻辑从哪来呢?
合理的做法应该把所有接口方法桥接收拢,统一代理给一个接口方法 invoke()
,将 方法名称和参数 作为 invoke()
方法的参数,这样动态代理的使用者就可以针对 methodName 进行对应处理。
现在你可以猜想一下动态生成的代理类的具体结构:
InvocationHandler.invoke()
用伪代码表示:
public class DynamicProxy implements ICount {
private Method method1 = Class.forName("proxy.ICount").getMethod("test1");
private Method method2 = Class.forName("proxy.ICount").getMethod("test2");
private InvocationHandler handler;
public DynamicProxy(InvocationHandler handler) {
this.handler = handler;
}
@Override
public void test1() {
handler.invoke(this, method1, (Object[])null)
}
@Override
public void test2() {
handler.invoke(this, method2, (Object[])null)
}
}
除了一些代码细节,这其实已经很接近真正生成的动态代理类了。
我们再来看 JDK 中动态代理的使用方法:
Counter counter = new Counter();
ICount proxyCount = (ICount) Proxy.newProxyInstance(counter.getClass().getClassLoader(), counter.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object result = method.invoke(counter, args);
long end = System.currentTimeMillis();
System.out.println(method.getName() + " consume: " + (end - start));
return result;
}
});
proxyCount.test1();
proxyCount.test2();
核心点在于通过 Proxy.newProxyInstance()
动态生成代理类。它有三个参数:
ClassLoader loader
: 类加载器Class<?>[] interfaces
:要代理的接口InvocationHandler h
: 所有接口方法会被桥接到 InvocationHandler 的 invoke()
方法。invoke()
方法有三个参数。proxy
是代理类,method
是代理的方法,args
是代理方法的参数。既满足了对代理方法的统一处理,也可以针对 method 做单独处理。
完全符合我们之前的伪代码。
通过下面的 JDK 的参数配置,可以在当前目录直接生成动态代理类的 class 文件。
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
你可以在 这里 查看生成的 com.sun.proxy.$Proxy0.class
文件。
动态代理的原理很简单,运行时动态生成并加载代理类 。我们跟进源码,再加深一下印象。
PS:以下代码基于 OpenJdk 15 。
从 Proxy.newProxyInstance()
开始。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
// 1. 获取代理类的 Constructor
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
// 2. 创建代理类实例
return newProxyInstance(caller, cons, h);
}
先看代码 2 处的 newProxyInstance()
:
private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
Constructor<?> cons,
InvocationHandler h) {
try {
if (caller != null) {
// 1. 权限检查
checkNewProxyPermission(caller, cons.getDeclaringClass());
}
// 2. 创建代理类实例
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException | InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
......
}
}
直接调用 Constructor.newInstance()
方法创建代理类实例,注意构造器参数是传入的 InvocationHandler
对象。
重点在于如何获取代理类的构造器?再回到代码 1 处的 getProxyConstructor()
。
private static Constructor<?> getProxyConstructor(Class<?> caller,
ClassLoader loader,
Class<?>... interfaces)
{
// 单接口的优化处理
// 为了方便,我们直接看这里
if (interfaces.length == 1) {
Class<?> intf = interfaces[0];
if (caller != null) {
checkProxyAccess(caller, loader, intf);
}
// 从 proxyCache 中获取,或者通过 ProxyBuilder 新建
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
} else {
......
}
}
这里提供了 Constructor 的缓存类 proxyCache
避免重复生成。如果缓存中没有,就通过 ProxyBuilder
获取构造器,传入的参数是 Classloader 和 Interface 。
直接看 ProxyBuilder.build()
方法。
Constructor<?> build() {
// 1. 获取代理类的 Class 对象
Class<?> proxyClass = defineProxyClass(module, interfaces);
final Constructor<?> cons;
try {
// 2. 根据代理类的 Class 对象获取构造器
cons = proxyClass.getConstructor(constructorParams);
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
......
return cons;
}
核心在于获取代理类 Class 对象的 defineProxyClass()
方法。
private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
String proxyPkg = null;
// 代理类是 public final 的
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
......
if (proxyPkg == null) {
// PROXY_PACKAGE_PREFIX 值为 com.sun.proxy
proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
: PROXY_PACKAGE_PREFIX;
} else if (proxyPkg.isEmpty() && m.isNamed()) {
throw new IllegalArgumentException(
"Unnamed package cannot be added to " + m);
}
......
/*
* proxyClassNamePrefix 值为 $Proxy
* 生成的代理类名 com.sun.proxy.$Proxy0
* 结尾数字从 0 开始递增
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg.isEmpty()
? proxyClassNamePrefix + num
: proxyPkg + "." + proxyClassNamePrefix + num;
......
/*
* 生成代理类字节码
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
try {
Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
0, proxyClassFile.length,
loader, null);
reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
return pc;
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
核心在于生成代理类字节码的 ProxyGenerator.generateProxyClass()
方法,有三个参数:
name
: 代理类名称,$Proxy0interfaces
: 代理接口,数组accessFlags
:Modifier.PUBLIC | Modifier.FINALstatic byte[] generateProxyClass(ClassLoader loader, final String name, List<Class<?>> interfaces, int accessFlags) {
ProxyGenerator gen = new ProxyGenerator(loader, name, interfaces, accessFlags);
// 生成字节码文件
final byte[] classFile = gen.generateClassFile();
if (saveGeneratedFiles) {
/*
* 可以通过配置 jdk.proxy.ProxyGenerator.saveGeneratedFiles 参数
* 将字节码文件输出到本地文件
*/
}
return classFile;
}
再跟进 ProxyGenerator.generateClassFile()
。
private byte[] generateClassFile() {
this.visit(58, this.accessFlags, dotToSlash(this.className), (String)null, "java/lang/reflect/Proxy", typeNames(this.interfaces));
// 添加 hashCode 方法
this.addProxyMethod(hashCodeMethod);
// 添加 equals 方法
this.addProxyMethod(equalsMethod);
// 添加 toString 方法
this.addProxyMethod(toStringMethod);
Iterator var1 = this.interfaces.iterator();
while(var1.hasNext()) {
Class<?> intf = (Class)var1.next();
Method[] var3 = intf.getMethods();
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
Method m = var3[var5];
if (!Modifier.isStatic(m.getModifiers())) {
// 添加代理接口方法
this.addProxyMethod(m, intf);
}
}
}
var1 = this.proxyMethods.values().iterator();
List sigmethods;
while(var1.hasNext()) {
sigmethods = (List)var1.next();
checkReturnTypes(sigmethods);
}
// 生成构造器
this.generateConstructor();
var1 = this.proxyMethods.values().iterator();
while(var1.hasNext()) {
sigmethods = (List)var1.next();
Iterator var8 = sigmethods.iterator();
while(var8.hasNext()) {
ProxyMethod pm = (ProxyMethod)var8.next();
this.visitField(10, pm.methodFieldName, "Ljava/lang/reflect/Method;", (String)null, (Object)null);
pm.generateMethod(this, this.className);
}
}
// 生成静态代码块
this.generateStaticInitializer();
return this.toByteArray();
}
ProxyGenerator
继承自 ClassWriter
,通过 ASM 生成了字节码。这块就不细看了,具体 API 我也不是很清楚。挑一个生成构造器的 generateConstructor()
方法看一下:
private void generateConstructor() {
MethodVisitor ctor = this.visitMethod(1, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", (String)null, (String[])null);
ctor.visitParameter((String)null, 0);
ctor.visitCode();
ctor.visitVarInsn(25, 0);
ctor.visitVarInsn(25, 1);
ctor.visitMethodInsn(183, "java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", false);
ctor.visitInsn(177);
ctor.visitMaxs(-1, -1);
ctor.visitEnd();
}
可以看到确实是将 InvocationHandler
作为了构造器参数,但并不是直接给代理类生成的,而是 Proxy
类。代理类是继承自 Proxy
类,并引用父类的构造器。
生成代理类字节码文件之后,通过 UnSafe.defineClass()
注册到 VM 。
public Class<?> defineClass(String name, byte[] b, int off, int len,
ClassLoader loader,
ProtectionDomain protectionDomain) {
if (b == null) {
throw new NullPointerException();
}
if (len < 0) {
throw new ArrayIndexOutOfBoundsException();
}
return defineClass0(name, b, off, len, loader, protectionDomain);
}
public native Class<?> defineClass0(String name, byte[] b, int off, int len,
ClassLoader loader,
ProtectionDomain protectionDomain);
到这,动态代理的流程就跟完了。
经典提问环节:动态代理只能代理接口吗?如果是,为什么?
在评论区,留下你的答案吧!