高効率反射ReflectASMの原理学習


ReflectAS
M原理学習
最近,静的エージェント,動的エージェントなどの技術を学び,さらにASM jarパケットを知り,このjarパケットをベースに開発された高性能の反射パケットReflectASMを知る.自分が前にコードを書いたときに使っていたのはjavaが持参した反射パケットだったことを思い出すと性能がもったいないが、このjarパケット番号はほとんど直接コードにメソッドを呼び出すような性能だと言い、勉強の心を奮い立たせた.
ReflectASMはこんなに不思議ですか?反射を使ってもそんなに効率的ですか?
jarパッケージを開くと実は4つのクラスで、確かにソースコードを非常に軽視しています.反射呼び出し方法だけではAccessClassLaderとMethodAccessの2つのクラスしか使われていません.
反射ターゲット
package cn.lsg.codegenerate;

public class Demo {

    private String a;
    private Integer b;

    public String getA() {
        return a;
    }

    public Integer getB() {
        return b;
    }

    public void setA(String a) {
        this.a = a;
    }

    public void setB(Integer b) {
        this.b = b;
    }

}

mainメソッド
 public static void main(String[] args) throws IOException {
        Demo d=new Demo();
        d.setA("asasa");
        d.setB(50);
        MethodAccess m = MethodAccess.get(Demo.class);
        Object invoke = m.invoke(d, "getA");
        System.out.println(invoke);
    }

MethodAccessのソースコードを読むと、その実行プロセスがわかります.
  • 反射Demo.class共通非静的メソッド
  • を取得
  • ASM技術を用いてMethodAccessに継承する新しいクラスDemoMethodAccess
  • を動的に生成する.
  • プライベート変数にメソッド名パラメータ等の情報
  • を記録する.
  • クラスのinvokeメソッド内で異なるメソッドを呼び出すことを実装したいinvokeメソッドの内容を見てみたいと思いますが、実装が困難であることがわかりました.最初はオブジェクトストリームを使用してclassオブジェクトを保存しようとしましたが、classはシーケンス化インタフェースを実装していないことがわかり、ブレークポイントデバッグもメソッドにアクセスできません.その後、巧みな方法を考えて、新しいクラスを直接確立してMethodAccessコンテンツをコピーし、コード
    static public MethodAccess get (Class type) {
       ......
        synchronized (loader) {
            try {
                accessClass = loader.loadClass(accessClassName);
            } catch (ClassNotFoundException ignored) {
                String accessClassNameInternal = accessClassName.replace('.', '/');
                String classNameInternal = className.replace('.', '/');
    
                ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                MethodVisitor mv;
                //           (    )     cn/lsg/codegenerate/MethodAccess
                cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, "cn/lsg/codegenerate/MethodAccess",
                        new String[]{"java/io/Serializable"});
             ......
                byte[] data = cw.toByteArray();
                //     
                FileOutputStream fout;
                try {
                    fout = new FileOutputStream("C:/Users/Administrator/Desktop/     /"+accessClassName+".class");
                    fout.write(data);
                    fout.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                accessClass = loader.defineClass(accessClassName, data);
            }
            //     
    
        }
        try {
            MethodAccess access = (MethodAccess)accessClass.newInstance();
            access.methodNames = methodNames;
            access.parameterTypes = parameterTypes;
            access.returnTypes = returnTypes;
            return access;
        } catch (Exception ex) {
            throw new RuntimeException("Error constructing method access class: " + accessClassName, ex);
        }
    }
  • を修正しました.
    オブジェクトを返す前にバイトコードを格納するbyte配列をファイルに出力します.生成したclassファイルを逆コンパイラで逆コンパイルしてやっと実現方式を知った
    package cn.lsg.codegenerate;
    
    import java.io.Serializable;
    
    public class DemoMethodAccess extends MethodAccess
      implements Serializable
    {
      public Object invoke(Object paramObject, int paramInt, Object[] paramArrayOfObject)
      {
        Demo localDemo = (Demo)paramObject;
        switch (paramInt)
        {
        case 0:
          localDemo.setA((String)paramArrayOfObject[0]);
          return null;
        case 1:
          localDemo.setB((Integer)paramArrayOfObject[0]);
          return null;
        case 2:
          return localDemo.getA();
        case 3:
          return localDemo.getB();
        }
        throw new IllegalArgumentException("Method not found: " + paramInt);
      }
    }

    なるほど、直接呼び出しに匹敵する性能があるのも無理はありません.これは直接呼び出し方法ですが、呼び出し方法の前に反射を使い、バイトコードを生成したり、新しいクラスのロードをしたりするのに時間がかかります.開始から終了までのすべての時間を計算すると、他の反射よりもあまり優れていませんが、エージェントクラスを1回だけ生成するとパフォーマンスが高くなります.このjarパッケージを使用するには、生成されたclassオブジェクトをキャッシュすることに注意してください.繰り返し生成を最小限に抑える.