1

出于好奇,我尝试导出 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),然后尝试重新定义类以获取字节码。

我尝试了相同的方法来导出生成的代理类,它完美地工作。这就是驱使我尝试这个的原因。

当我尝试使用 loadClass 方法加载类时,似乎 GeneratedMethodAccessor1 类的 DelegatingClassLoader 甚至无法重新加载该类。

有什么想法可以检索 GeneratedMethodAccessor 类的字节码吗?

4

1 回答 1

1

首先,这NullPointerException是一个错误,我刚刚修复了它。装载机应该抛出一个IllegalArgumentException,但它从来没有那么远。感谢您引起我的注意。

归根结底,Byte Buddy面临的问题是

gma.getClass().getClassLoader().findClass(gma.getClass().getName());

抛出一个ClassNotFoundException. DelegatingClassLoader这是对访问器类使用 a 的结果。作为一个有根据的猜测,我认为这个类加载器打算从外部屏蔽它的类,以便使它们容易被垃圾收集。但是,不允许查找在某种程度上违反了ClassLoader. 除此之外,我假设这个加载例程将被重构以在将来的某个时候使用 JDK 的匿名类加载器(类似于表示 lambda 表达式的类)。奇怪的是,在 JDK 中似乎没有源代码,DelegatingClassLoader即使我可以在发行版中找到它。可能,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直接使用 s 而不是应用重写,这实际上可能会稍微修改类文件,即使您不对类应用任何更改。

于 2015-03-17T07:42:26.307 回答