15

開発中のオープンソース プロジェクトへの反映を避けたい。ここでは、次のようなクラスがあります。

public class PurchaseOrder {

   @Property
   private Customer customer;

   @Property
   private String name;
}

注釈をスキャンして@Property、PurchaseOrder から反射的に設定および取得できるものを判断します。とをすべて使用するそのようなクラスが多数ありjava.lang.reflect.Field.get()ますjava.lang.reflect.Field.set()

理想的には、プロパティごとに次のような呼び出し元を生成したいと思います。

public interface PropertyAccessor<S, V> {
   public void set(S source, V value);
   public V get(S source);
}

PurchaseOrderクラスをスキャンすると、そのような静的内部クラスを作成できます。

static class customer_Field implements PropertyAccessor<PurchaseOrder, Customer> {
   public void set(PurchaseOrder order, Customer customer) {
      order.customer = customer;
   }  
   public Customer get(PurchaseOrder order) {
      return order.customer;
   }
}

これらを使用すると、反射のコストを完全に回避できます。ネイティブ パフォーマンスでインスタンスを設定および取得できるようになりました。誰かが私にこれを行う方法を教えてもらえますか。コード例は素晴らしいでしょう。良い例をネットで検索しましたが、このようなものは見つかりません。ASM と Javasist の例もかなり貧弱です。

ここで重要なのは、渡すことができるインターフェイスがあることです。したがって、Java リフレクションをデフォルトとして使用するもの、ASM を使用するもの、Javassist を使用するものなど、さまざまな実装を使用できますか?

どんな助けでも大歓迎です。

4

5 に答える 5

10

ASM

を使用ASMifierClassVisitorすると、内部クラスを生成するために記述する必要があるコードを正確に確認できます。

ASMifierClassVisitor.main(new String[] { PurchaseOrder.customer_Field.class
    .getName() });

あとは、ジェネレータ コードでパラメータ化する必要があるビットを決定するだけです。PurchaseOrder$customer_Fieldファイルになる出力例inject/PurchaseOrder$customer_Field.class:

public static byte[] dump () throws Exception {

  ClassWriter cw = new ClassWriter(0);
  FieldVisitor fv;
  MethodVisitor mv;
  AnnotationVisitor av0;

  cw.visit(V1_6, ACC_SUPER, "inject/PurchaseOrder$customer_Field",
      "Ljava/lang/Object;"+
      "Linject/PropertyAccessor<Linject/PurchaseOrder;Linject/Customer;>;", 
      "java/lang/Object",
      new String[] { "inject/PropertyAccessor" });
//etc

(パッケージとして「注入」を使用しました。)

また、ASM のビジター クラスを使用して合成アクセサーを作成する必要があります。

{
mv = cw.visitMethod(ACC_STATIC + ACC_SYNTHETIC, "access$0", 
          "(Linject/PurchaseOrder;Linject/Customer;)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, "inject/PurchaseOrder",
          "customer", "Linject/Customer;");
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_STATIC + ACC_SYNTHETIC, "access$1", 
          "(Linject/PurchaseOrder;)Linject/Customer;", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "inject/PurchaseOrder", "
          customer", "Linject/Customer;");
mv.visitInsn(ARETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}

メソッドを注入する方法の例については、このプロジェクトを参照してください。


これらを使用すると、反射のコストを完全に回避できます。

これはすべて実行時に行われるため、次のようになります。

  • この解析とコード生成には初期費用がかかります
  • これらの生成されたタイプを何らかの形で発見して内省する必要があります
于 2010-06-15T11:30:24.770 に答える
4

Javassist を使用した例ですが、プロパティが非公開ではなくパッケージ レベルで保護されている必要があります。

public class AccessorGenerator {

    private final ClassPool pool;

    public PropertyGenerator() {
        pool = new ClassPool();
        pool.appendSystemPath();
    }

    public Map<String, PropertyAccessor> createAccessors(Class<?> klazz) throws Exception {
        Field[] fields = klazz.getDeclaredFields();

        Map<String, PropertyAccessor> temp = new HashMap<String, PropertyAccessor>();
        for (Field field : fields) {
            PropertyAccessor accessor = createAccessor(klazz, field);
            temp.put(field.getName(), accessor);
        }

        return Collections.unmodifiableMap(temp);
    }

    private PropertyAccessor createAccessor(Class<?> klazz, Field field) throws Exception {
        final String classTemplate = "%s_%s_accessor";
        final String getTemplate = "public Object get(Object source) { return ((%s)source).%s; }";
        final String setTemplate = "public void set(Object dest, Object value) { return ((%s)dest).%s = (%s) value; }";

        final String getMethod = String.format(getTemplate, 
                                               klazz.getName(),
                                               field.getName());
        final String setMethod = String.format(setTemplate, 
                                               klazz.getName(), 
                                               field.getName(), 
                                               field.getType().getName());

        final String className = String.format(classTemplate, klazz.getName(), field.getName());

        CtClass ctClass = pool.makeClass(className);
        ctClass.addMethod(CtNewMethod.make(getMethod, ctClass));
        ctClass.addMethod(CtNewMethod.make(setMethod, ctClass));
        ctClass.setInterfaces(new CtClass[] { pool.get(PropertyAccessor.class.getName()) });
        Class<?> generated = ctClass.toClass();
        return (PropertyAccessor) generated.newInstance();
    }

    public static void main(String[] args) throws Exception {
        AccessorGenerator generator = new AccessorGenerator();

        Map<String, PropertyAccessor> accessorsByName = generator.createAccessors(PurchaseOrder.class);

        PurchaseOrder purchaseOrder = new PurchaseOrder("foo", new Customer());

        accessorsByName.get("name").set(purchaseOrder, "bar");
        String name = (String) accessorsByName.get("name").get(purchaseOrder);
        System.out.println(name);
    }
}
于 2010-06-19T09:26:53.773 に答える
1

注釈プロセッサも使用できるため、バイトコード操作の複雑さを回避できます。( javabeat に関するこの記事を参照してください)

于 2010-06-11T12:59:40.497 に答える
0

反射が非常に遅いことに驚いています。JVM をウォームアップすると、直接アクセスよりも 5 倍以上遅くなることはありません。ところで、マイクロベンチマークは、実際の作業を行わない場合、単純なゲッター/セッターを簡単に最適化してゼロにすることができるため、誤解を招く結果をもたらす可能性があります。

リフレクションとバイト コードを回避する別の方法は、sun.misc.Unsafe クラスを使用することです。注意して処理する必要があり、すべての JVM に移植できるわけではありませんが、リフレクションよりも 2 ~ 3 倍高速です。例については、私の Essence-rmi プロジェクトを参照してください。

もう 1 つのオプションは、コードを生成してその場でコンパイルすることです。Compiler API または BeanShell などのライブラリを使用できます。

注: プライベート フィールドがある場合、バイト コードを使用して別のクラスからアクセスすることはできません。これは JVM の制限です。内部およびネストされたクラスは、プライベート メソッドを持つクラスで access$100 のようなアクセサー メソッドを生成することでこれを回避します (コール スタックでこれらを見たことがあるかもしれません)。クラス。

于 2010-06-12T09:40:00.290 に答える
-2

目標はパフォーマンス!

はい、多くの場合、それが目標です。しかし、PropertyAccessor を使用して現在行っていることは、パフォーマンスが低下します! プロパティを取得または設定するたびに、 の新しいインスタンスを作成する必要がありますcustomer_Field。または、インスタンスを保持する必要があります。単純なゲッターまたはセッターの問題が何であるかわかりません。

public class PurchaseOrder {

   @Property
   private Customer customer;

   @Property
   private String name;

   pulic void setCustomer(Customer c)
   {
       this.customer = c;
   }

   public Customer getCustomer()
   {
       return customer;
   }

   // The same for name
}

これぞパフォーマンス!ネイティブ コードはおそらく 14 倍高速ですが、本当にその速度が必要ですか? Javaは素晴らしいです。なんで?プラットフォームに依存しないためです。ネイティブなものを作ろうとすると、Java の力はなくなります。では、プログラムが実行する必要があることをすべて実行するのに 1 分間待機することと、50 秒待機することの違いは何でしょうか。「私の 14 倍速いのはどこですか?」取得して設定する必要があるだけではありません。すべてのデータで何かをする必要があります。

オブジェクトのインスタンスとプリミティブを取得して設定しているだけなので、それが高速になるとは思いません。ネイティブ Java は次の目的で作成されます。

  • Java ランタイム環境よりも機械語で非常に高速な何かを計算する必要があるメソッド (のjava.lang.Mathような多くのメソッドsqrt()。Java でプログラムすることはできますが、遅くなります)
  • アプリケーションの終了、ソケットの作成、ファイルの書き込み/読み取り、他のプロセスの呼び出しなど、Java が単独で実行できないことは、それを実行するネイティブ マシン コードである純粋な Java ではありません。

だから私はあなたを説得してJavaでそれを守ってくれることを願っています.

于 2010-06-15T11:07:20.630 に答える