【问题标题】:Serializing a proxy class to file将代理类序列化为文件
【发布时间】:2022-02-21 12:04:15
【问题描述】:
我有一个在运行时在 JVM 中生成的代理(生成为 JDKProxy 或 CGLIB)。我想知道是否有办法将这个类的内容(看起来像 com.sun.proxy$Proxy123.class)写入文件,以便我可以使用 jd-eclipse 之类的反编译器来查看代码的种类生成。由于该类存在于 JVM 中,我想知道是否有一种方法可以让 ClassLoader 为实际类提供 InputStream/URL,然后可以使用该类将内容写入磁盘 - 以及磁盘上的这个文件可以使用 jd-eclipse 或 javap 读取。我知道这不是一个生产用例,但我很想看看这个动态生成的类的内容。
谢谢!
【问题讨论】:
标签:
java
proxy-classes
cglib
【解决方案1】:
您可以使用Instrumentation 到register 一个可重新转换的ClassFileTransformer 并请求Proxy 类的re-transformation。然后,在转换器的transform method 中,您可以使用构成类的字节数组。将数组保存到类文件后,您可以简单地返回未修改的数组,让 JVM 不受影响地继续。
但我不确定您对这些课程有何期望。它们被直接实施并且没有提供任何惊喜。以下是在 Oracle 的 jdk1.7.0_40 生成的 java.lang.Runnable Proxy 上执行上述步骤后 javap 的输出示例:
public final class com.sun.proxy.$Proxy0 extends java.lang.reflect.Proxy implements java.lang.Runnable {
private static java.lang.reflect.Method m1;
private static java.lang.reflect.Method m3;
private static java.lang.reflect.Method m0;
private static java.lang.reflect.Method m2;
public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler);
Code:
0: aload_0
1: aload_1
2: invokespecial #8 // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
5: return
public final int hashCode();
Code:
0: aload_0
1: getfield #16 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
4: aload_0
5: getstatic #55 // Field m0:Ljava/lang/reflect/Method;
8: aconst_null
9: invokeinterface #28, 4 // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
14: checkcast #57 // class java/lang/Integer
17: invokevirtual #60 // Method java/lang/Integer.intValue:()I
20: ireturn
21: athrow
22: astore_1
23: new #42 // class java/lang/reflect/UndeclaredThrowableException
26: dup
27: aload_1
28: invokespecial #45 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
31: athrow
Exception table:
from to target type
0 21 21 Class java/lang/Error
0 21 21 Class java/lang/RuntimeException
0 21 22 Class java/lang/Throwable
public final boolean equals(java.lang.Object);
Code:
0: aload_0
1: getfield #16 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
4: aload_0
5: getstatic #20 // Field m1:Ljava/lang/reflect/Method;
8: iconst_1
9: anewarray #22 // class java/lang/Object
12: dup
13: iconst_0
14: aload_1
15: aastore
16: invokeinterface #28, 4 // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
21: checkcast #30 // class java/lang/Boolean
24: invokevirtual #34 // Method java/lang/Boolean.booleanValue:()Z
27: ireturn
28: athrow
29: astore_2
30: new #42 // class java/lang/reflect/UndeclaredThrowableException
33: dup
34: aload_2
35: invokespecial #45 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
38: athrow
Exception table:
from to target type
0 28 28 Class java/lang/Error
0 28 28 Class java/lang/RuntimeException
0 28 29 Class java/lang/Throwable
public final java.lang.String toString();
Code:
0: aload_0
1: getfield #16 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
4: aload_0
5: getstatic #65 // Field m2:Ljava/lang/reflect/Method;
8: aconst_null
9: invokeinterface #28, 4 // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
14: checkcast #67 // class java/lang/String
17: areturn
18: athrow
19: astore_1
20: new #42 // class java/lang/reflect/UndeclaredThrowableException
23: dup
24: aload_1
25: invokespecial #45 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
28: athrow
Exception table:
from to target type
0 18 18 Class java/lang/Error
0 18 18 Class java/lang/RuntimeException
0 18 19 Class java/lang/Throwable
static {};
Code:
0: ldc #70 // String java.lang.Object
2: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
5: ldc #77 // String equals
7: iconst_1
8: anewarray #72 // class java/lang/Class
11: dup
12: iconst_0
13: ldc #70 // String java.lang.Object
15: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
18: aastore
19: invokevirtual #81 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
22: putstatic #20 // Field m1:Ljava/lang/reflect/Method;
25: ldc #83 // String java.lang.Runnable
27: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
30: ldc #84 // String run
32: iconst_0
33: anewarray #72 // class java/lang/Class
36: invokevirtual #81 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
39: putstatic #50 // Field m3:Ljava/lang/reflect/Method;
42: ldc #70 // String java.lang.Object
44: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
47: ldc #85 // String hashCode
49: iconst_0
50: anewarray #72 // class java/lang/Class
53: invokevirtual #81 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
56: putstatic #55 // Field m0:Ljava/lang/reflect/Method;
59: ldc #70 // String java.lang.Object
61: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
64: ldc #86 // String toString
66: iconst_0
67: anewarray #72 // class java/lang/Class
70: invokevirtual #81 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
73: putstatic #65 // Field m2:Ljava/lang/reflect/Method;
76: return
77: astore_1
78: new #90 // class java/lang/NoSuchMethodError
81: dup
82: aload_1
83: invokevirtual #93 // Method java/lang/Throwable.getMessage:()Ljava/lang/String;
86: invokespecial #96 // Method java/lang/NoSuchMethodError."<init>":(Ljava/lang/String;)V
89: athrow
90: astore_1
91: new #100 // class java/lang/NoClassDefFoundError
94: dup
95: aload_1
96: invokevirtual #93 // Method java/lang/Throwable.getMessage:()Ljava/lang/String;
99: invokespecial #101 // Method java/lang/NoClassDefFoundError."<init>":(Ljava/lang/String;)V
102: athrow
Exception table:
from to target type
0 77 77 Class java/lang/NoSuchMethodException
0 77 90 Class java/lang/ClassNotFoundException
public final void run();
Code:
0: aload_0
1: getfield #16 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
4: aload_0
5: getstatic #50 // Field m3:Ljava/lang/reflect/Method;
8: aconst_null
9: invokeinterface #28, 4 // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
14: pop
15: return
16: athrow
17: astore_1
18: new #42 // class java/lang/reflect/UndeclaredThrowableException
21: dup
22: aload_1
23: invokespecial #45 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
26: athrow
Exception table:
from to target type
0 16 16 Class java/lang/Error
0 16 16 Class java/lang/RuntimeException
0 16 17 Class java/lang/Throwable
}
【解决方案2】:
Cglib 在幕后使用 ASM,并且可以获取由 ASM 创建的表示 cglib 生成的类的字节数组。 cglib Enhancer 类有一个方法 generateClass(ClassVisitor),您可以在其中传递任何 ASM ClassVisitor,对于您的情况,这将是 ClassWriter 的一个实例。只需在类生成后调用 ClassWriter#toByteArray() 并将从此方法接收到的字节保存在以生成的类命名的文件中,并以 .class 为后缀。
问题:Cglib 代理是通过传递一个到几个实现Callback 接口的实例来实现的。一旦创建并加载了代理类,这些实例就会被 cglib 注入到代理类的 static 字段中。但是,如果您在不使用 cglib 的情况下加载相同的类,则必须在加载类和第一次使用其中一个实例之间手动完成 Callbacks 的注入。否则,您的代理将不起作用,但由于缺少Callback,因此在任何拦截方法的调用上都会抛出NullPointerExceptions。还有更坏的消息:cglib 为Callbacks 创建了一个随机字段名称,这样注入相当困难。
对于 Java Proxys 也是如此,其中 MethodInterceptor 必须注入创建的代理中。然而,获取代表代理的字节数组就更难了。基本上,Proxy 实现不会为您提供任何访问权限,因此您必须调用ProxyGenerator#generateProxyClass(这是您不应该使用的内部 sun 包类)来创建字节数组。然而,InvocationHandler 的注入相当简单,如 javadoc 中所述:
Foo f = (Foo) proxyClass.
getConstructor(new Class[] { InvocationHandler.class }).
newInstance(new Object[] { handler });
最后,对于我上面描述的所有问题,我不建议您按照您的计划做任何事情。但是,如果您仍然想这样做,请确保仅通过某些封装工厂访问手动加载的代理类,该封装工厂强制代理在使用之前始终正确初始化。否则你将一直处理NullPointerExceptions 和奇怪的功能。一般来说,宁可在其他 VM 上重新创建代理,而不是尝试使它们正确可序列化。
您不能要求 ClassLoader 将字节数组吐出到它已加载的类,但如果您可以/想要使用检测 API,您可能还想寻求 Holger 的解决方案。
【解决方案3】:
JDK 代理
在 JDK11
将此添加到您的 aop 代码之前
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
或者,添加虚拟机选项:-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true
在ProxyGenerator.generateProxyClass()中,有一段代码决定是否将类文件输出到你的位置。
Cglib 代理
目前,我只能在调试时输出文件。但是我觉得做一次就可以理解他们是如何完成拦截的了。
在 DefaultGeneratorStrategy.generate(ClassGenerator cg) 处添加断点。同时程序运行到这一行:
return this.transform(cw.toByteArray());
你可以用FileOutputStream来写cw.toByteArray()。它是cglib代理类,你可以用IDEA、jd gui、bytecode-viewer等工具反编译。