尝试重新定义sun.reflect.GeneratedMethodAccessor1时,ByteBuddy失败
在好奇心的驱使下,我试图导出GeneratedMethodAccessor1的字节码(使用reflection时由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 unloaded = new ByteBuddy().redefine( gma.getClass(), classFileLocator).make(); Map 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类的字节码?
首先, NullPointerException
是一个错误,我只是解决了这个问题。 加载器应该抛出一个IllegalArgumentException
但它永远不会那么远。 谢谢你引起我的注意。
归结起来,Byte Buddy面临的问题就在于此
gma.getClass().getClassLoader().findClass(gma.getClass().getName());
抛出ClassNotFoundException
。 这是使用DelegatingClassLoader
作为访问者类的结果。 作为一个有根据的猜测,我认为这个类加载器打算从外部屏蔽它的类,以使它们容易被垃圾收集。 但是,不允许查找类有点违反ClassLoader
的合同。 除此之外,我假设这个加载例程将被重构为在将来的某个时候使用JDK的匿名类加载器(类似于表示lambda表达式的类)。 奇怪的是,似乎DelegatingClassLoader
的源代码在JDK中不可用,即使我可以在发行版中找到它。 可能VM特别在某个地方处理这些装载机。
现在,您可以使用以下ClassFileTransformer
,它在类加载器上使用一些reflection魔法来定位加载的类,然后提取字节数组。 ( 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
,而不是应用重写,即使您没有对类应用任何更改,也可能稍微修改类文件。