出于好奇心,我试图导出GeneratedMethodAccessor1的字节码(使用反射时由JVM生成)。
我尝试按以下方式获取类的字节码:
public class MethodExtractor {
public static void main(String[] args) throws Exception {
ExampleClass example = new ExampleClass();
Method exampleMethod = ExampleClass.class
.getDeclaredMethod("exampleMethod");
exampleMethod.setAccessible(true);
int rndSum = 0;
for (int i = 0; i < 20; i++) {
rndSum += (Integer) exampleMethod.invoke(example);
}
Field field = Method.class.getDeclaredField("methodAccessor");
field.setAccessible(true);
Object methodAccessor = field.get(exampleMethod);
Field delegate = methodAccessor.getClass().getDeclaredField("delegate");
delegate.setAccessible(true);
Object gma = delegate.get(methodAccessor);
ByteBuddyAgent.installOnOpenJDK();
try {
ClassFileLocator classFileLocator = ClassFileLocator.AgentBased
.fromInstalledAgent(gma.getClass().getClassLoader());
Unloaded<? extends Object> unloaded = new ByteBuddy().redefine(
gma.getClass(), classFileLocator).make();
Map<TypeDescription, File> saved = unloaded.saveIn(Files
.createTempDirectory("javaproxy").toFile());
saved.forEach((t, u) -> System.out.println(u.getAbsolutePath()));
} catch (IOException e) {
throw new RuntimeException("Failed to save class to file");
}
}
}
但是,我在执行此类时遇到以下错误:
Exception in thread "main" java.lang.NullPointerException
at net.bytebuddy.dynamic.scaffold.TypeWriter$Engine$ForRedefinition.create(TypeWriter.java:172)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1182)
at net.bytebuddy.dynamic.scaffold.inline.InlineDynamicTypeBuilder.make(InlineDynamicTypeBuilder.java:244)
at reegnz.dyna.proxy.extractor.MethodExtractor.main(MethodExtractor.java:48)
基本上我首先在方法调用上迭代足够多次让JVM对方法进行膨胀(生成GeneratedMethodAccessor),然后尝试重新定义类以获取字节码。
我尝试使用相同的方法导出生成的Proxy类,并且它运行完美。这就是驱使我尝试这一点的原因。
当我尝试使用loadClass方法加载类时,似乎GeneratedMethodAccessor1类的DelegatingClassLoader甚至无法重新加载该类。
我是如何检索GeneratedMethodAccessor类的字节码的?
答案 0 :(得分:1)
首先,NullPointerException
是一个错误,我只是解决了这个问题。加载器应该抛出一个IllegalArgumentException
而不是那么远。谢谢你引起我的注意。
归结起来,Byte Buddy面临的问题是
gma.getClass().getClassLoader().findClass(gma.getClass().getName());
抛出ClassNotFoundException
。这是使用DelegatingClassLoader
作为访问者类的结果。作为一个有根据的猜测,我认为这个类加载器打算从外部屏蔽它的类,以使它们容易被垃圾收集。但是,不允许查找类有点违反ClassLoader
的合同。除此之外,我假设这个加载例程将被重构为在将来的某个时候使用JDK的匿名类加载器(类似于表示lambda表达式的类)。奇怪的是,它看起来像the source code for the DelegatingClassLoader
is not available in the JDK,尽管我可以在发行版中找到它。也许VM会在某个地方特别对待这些装载机。
现在,您可以使用以下ClassFileTransformer
,它在类加载器上使用一些反射魔法来定位加载的类,然后提取字节数组。 (ClassFileLocator
接口只接受名称而不是加载的类,以便允许处理卸载的类型,这通常是这种情况。不知道为什么在这种情况下这不起作用。)
class DelegateExtractor extends ClassFileLocator.AgentBased {
private final ClassLoader classLoader;
private final Instrumentation instrumentation;
public DelegateExtractor(ClassLoader classLoader, Instrumentation instrumentation) {
super(classLoader, instrumentation);
this.classLoader = classLoader;
this.instrumentation = instrumentation;
}
@Override
public Resolution locate(String typeName) {
try {
ExtractionClassFileTransformer classFileTransformer =
new ExtractionClassFileTransformer(classLoader, typeName);
try {
instrumentation.addTransformer(classFileTransformer, true);
// Start nasty hack
Field field = ClassLoader.class.getDeclaredField("classes");
field.setAccessible(true);
instrumentation.retransformClasses(
(Class<?>) ((Vector<?>) field.get(classLoader)).get(0));
// End nasty hack
byte[] binaryRepresentation = classFileTransformer.getBinaryRepresentation();
return binaryRepresentation == null
? Resolution.Illegal.INSTANCE
: new Resolution.Explicit(binaryRepresentation);
} finally {
instrumentation.removeTransformer(classFileTransformer);
}
} catch (Exception ignored) {
return Resolution.Illegal.INSTANCE;
}
}
}
为了进一步简化代码,您可以直接使用ClassFileLocator
而不是应用重写,即使您没有对类应用任何更改,也可能稍微修改类文件。 / p>