Alternatif untuk java.lang.reflect.Proxy untuk membuat proxy kelas abstrak (bukan antarmuka)

89

Menurut dokumentasi :

[ java.lang.reflect.] Proxymenyediakan metode statis untuk membuat kelas dan instance proxy dinamis, dan ini juga merupakan superclass dari semua kelas proxy dinamis yang dibuat dengan metode tersebut.

The newProxyMethodMetode (bertanggung jawab untuk menghasilkan proxy dinamis) memiliki tanda tangan berikut:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
                             throws IllegalArgumentException

Sayangnya, hal ini mencegah seseorang menghasilkan proxy dinamis yang memperluas kelas abstrak tertentu (daripada mengimplementasikan antarmuka tertentu). Ini masuk akal, mengingat java.lang.reflect.Proxyadalah "kelas super dari semua proxy dinamis", sehingga mencegah kelas lain menjadi kelas super tersebut.

Oleh karena itu, apakah ada alternatif java.lang.reflect.Proxyyang dapat menghasilkan proxy dinamis yang mewarisi dari kelas abstrak tertentu, mengarahkan semua panggilan ke metode abstrak ke penangan permintaan?

Misalnya, saya memiliki kelas abstrak Dog:

public abstract class Dog {

    public void bark() {
        System.out.println("Woof!");
    }

    public abstract void fetch();

}

Apakah ada kelas yang mengizinkan saya melakukan hal berikut?

Dog dog = SomeOtherProxy.newProxyInstance(classLoader, Dog.class, h);

dog.fetch(); // Will be handled by the invocation handler
dog.bark();  // Will NOT be handled by the invocation handler
Adam Paynter
sumber

Jawaban:

123

Itu bisa dilakukan dengan menggunakan Javassist (lihat ProxyFactory) atau CGLIB .

Contoh Adam menggunakan Javassist:

Saya (Adam Paynter) menulis kode ini menggunakan Javassist:

ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(Dog.class);
factory.setFilter(
    new MethodFilter() {
        @Override
        public boolean isHandled(Method method) {
            return Modifier.isAbstract(method.getModifiers());
        }
    }
);

MethodHandler handler = new MethodHandler() {
    @Override
    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
        System.out.println("Handling " + thisMethod + " via the method handler");
        return null;
    }
};

Dog dog = (Dog) factory.create(new Class<?>[0], new Object[0], handler);
dog.bark();
dog.fetch();

Yang menghasilkan keluaran ini:

Pakan!
Menangani mock.Dog.fetch () public abstract void melalui metode handler
axtavt
sumber
10
+1: Persis yang saya butuhkan! Saya akan mengedit jawaban Anda dengan kode sampel saya.
Adam Paynter
proxyFactory.setHandler()sudah ditinggalkan. Silakan gunakan proxy.setHandler.
AlikElzin-kilaka
@axtavt adalah objek "Dog" sebuah implementasi atau antarmuka dalam kode di atas?
stackoverflow
-7

Apa yang dapat Anda lakukan dalam kasus seperti itu adalah memiliki penangan proxy yang akan mengarahkan panggilan ke metode yang ada dari kelas abstrak Anda.

Anda tentu saja harus mengkodekannya, namun ini cukup sederhana. Untuk membuat Proxy Anda, Anda harus memberinya InvocationHandler. Anda kemudian hanya perlu memeriksa jenis metode dalam invoke(..)metode penangan permintaan Anda. Namun berhati-hatilah: Anda harus memeriksa jenis metode terhadap objek pokok yang terkait dengan penangan Anda, dan bukan terhadap jenis yang dideklarasikan dari kelas abstrak Anda.

Jika saya mengambil contoh kelas anjing Anda, metode pemanggilan penangan doa Anda mungkin terlihat seperti ini (dengan subkelas anjing terkait yang ada disebut .. yah ... dog)

public void invoke(Object proxy, Method method, Object[] args) {
    if(!Modifier.isAbstract(method.getModifiers())) {
        method.invoke(dog, args); // with the correct exception handling
    } else {
        // what can we do with abstract methods ?
    }
}

Namun, ada sesuatu yang membuat saya bertanya-tanya: Saya telah berbicara tentang suatu dogobjek. Namun, karena kelas Dog bersifat abstrak, Anda tidak dapat membuat instance, jadi Anda memiliki subkelas yang sudah ada. Selain itu, seperti yang ditunjukkan oleh pemeriksaan kode sumber Proxy yang cermat, Anda mungkin menemukan (di Proxy.java:362) bahwa tidak mungkin membuat Proxy untuk objek Kelas yang tidak mewakili antarmuka).

Jadi, terlepas dari kenyataan , apa yang ingin Anda lakukan adalah mungkin.

Riduidel
sumber
1
Mohon bersabar sementara saya mencoba untuk memahami jawaban Anda ... Dalam kasus khusus saya, saya ingin kelas proxy (dibuat saat runtime) menjadi subkelas Dog(misalnya, saya tidak secara eksplisit menulis Poodlekelas yang mengimplementasikan fetch()). Oleh karena itu, tidak ada dogvariabel untuk memanggil metode di atas ... Maaf jika saya bingung, saya harus memikirkan ini sedikit lagi.
Adam Paynter
1
@Adam - Anda tidak dapat secara dinamis membuat subclass saat runtime tanpa manipulasi bytecode (CGLib saya pikir melakukan sesuatu seperti ini). Jawaban singkatnya adalah proxy dinamis mendukung antarmuka, tetapi bukan kelas abstrak, karena keduanya merupakan konsep yang sangat berbeda. Hampir tidak mungkin untuk memikirkan cara untuk proxy kelas abstrak secara dinamis dengan cara yang waras.
Andrzej Doyle
1
@Andrzej: Saya memahami bahwa apa yang saya minta memerlukan manipulasi bytecode (sebenarnya, saya telah menulis solusi untuk masalah saya menggunakan ASM). Saya juga memahami bahwa proxy dinamis Java hanya mendukung antarmuka. Mungkin pertanyaan saya tidak sepenuhnya jelas - saya bertanya apakah ada kelas lain (yaitu, sesuatu selain java.lang.reflect.Proxy) tersedia yang melakukan apa yang saya butuhkan.
Adam Paynter
2
Nah, untuk mempersingkat waktu ... tidak (setidaknya di kelas Java standar). Menggunakan manipulasi bytecode, langit adalah batasnya!
Riduidel
9
Saya tidak memilih karena ini bukan jawaban atas pertanyaan. OP menyatakan bahwa dia ingin membuat proxy kelas, bukan antarmuka, dan sadar bahwa itu tidak mungkin dilakukan dengan java.lang.reflect.Proxy. Anda hanya mengulangi fakta itu dan tidak menawarkan solusi lain.
jcsahnwaldt Memulihkan Monica