java.lang.reflect.Proxyで生成されるバイトコードをファイルに出力する方法

いつから実装されたのか分からないが ファイルに出力する方法があったので書いておく。

やり方としては以下のパラメータを渡すだけ

  • -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true を引数で渡すだけ(※)

実際に動作確認をしたのは以下のJava。 他の環境では動かないかもしれない。

java -version
openjdk version "11.0.5" 2019-10-15
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.5+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.5+10, mixed mode)

実際にやってみる。 用意したのは以下のようなコードだ。

public class Test {

    public static void main(String... _args) {
        java.lang.reflect.Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{ MyInterface.class }, (proxy, method, args) -> method.invoke(proxy, args));
    }
}

interface MyInterface {}

適当なインターフェースを用意して Proxyを生成するだけのコードだ。

実際に実行してみる

$ javac Test.java
$ ls 
MyInterface.class   Test.class      Test.java
$ java -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true Test
$ ls
$Proxy0.class       MyInterface.class   Test.class      Test.java

$Proxy0.class とかいうファイルが増えている。

$ javap -p '$Proxy0.class'
final class $Proxy0 extends java.lang.reflect.Proxy implements MyInterface {
  private static java.lang.reflect.Method m1;
  private static java.lang.reflect.Method m2;
  private static java.lang.reflect.Method m0;
  public $Proxy0(java.lang.reflect.InvocationHandler) throws ;
  public final boolean equals(java.lang.Object) throws ;
  public final java.lang.String toString() throws ;
  public final int hashCode() throws ;
  static {} throws ;
}

実装は素朴で、InvocationHandlerをコンストラクタで受けて メソッドは、InvocationHandlerに移譲する形のコードになっている。

IntellijでFernflowerを使ってデコンパイルした結果が以下のものになる。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class $Proxy0 extends Proxy implements MyInterface {
    private static Method m1;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

コードリーディングした流れ

以前、Proxy周りのコードを読んだ時にDTraceを使えば、バイトコードを覗けそうなところまでは調べた。 生成されるバイトコードを見たくなったので、DTraceとかで見てみようかな、と思ったが、 どこに書いてあったか忘れてしまったので、もう1回読むことにした。(多分、Unsafe.defineClassとか経由で呼ばれているJVMのネイティブコードの中に入っていたように思う) ソースとしては、とりあえず見やすいadoptopenjdkがgithubに載せてあるソースを開いて読むことにした。

まず、java.lang.reflect.Proxyを以前読んだ時にバイトコードを生成しているのを知っていたので Proxy周りをサクッと流しつつ、バイトコードを生成した箇所を見つけた openjdk-jdk11/Proxy.java at master · AdoptOpenJDK/openjdk-jdk11 · GitHub

そこから上で呼び出しているProxyGeneratorのメソッドに移ってみた openjdk-jdk11/ProxyGenerator.java at 19fb8f93c59dfd791f62d41f332db9e306bc1422 · AdoptOpenJDK/openjdk-jdk11 · GitHub

saveGeneratedFiles なる変数があるので確認すると、static finalな変数だった。 openjdk-jdk11/ProxyGenerator.java at 19fb8f93c59dfd791f62d41f332db9e306bc1422 · AdoptOpenJDK/openjdk-jdk11 · GitHub

この変数は、jdk.proxy.ProxyGenerator.saveGeneratedFiles というシステムプロパティをbooleanにした値を保持しているようだ。

システムプロパティを設定すれば、Proxyが生成するバイトコードを見れることが分かった。

まとめ

jdk.proxy.ProxyGenerator.saveGeneratedFiles というシステムプロパティを設定すると java.lang.reflect.Proxyが生成するバイトコードがカレントディレクトリにファイル出力され 生成したバイトコードを見ることができる。

ちなみに ラムダ式バイトコードをランタイムに生成していて jdk.internal.lambda.dumpProxyClasses を設定すると同じようにバイトコードをファイル出力してくれる。 この辺にちょこっと書いてある。 ラムダ式、完全マスターした。 - 京都行きたい

追記

ダンプしとくと起動早くなるとかそんな素晴らしい機能はないです。