Proksi Dinamis di Java

1. Perkenalan

Artikel ini membahas tentang proxy dinamis Java - yang merupakan salah satu mekanisme proxy utama yang tersedia untuk kita dalam bahasa tersebut.

Sederhananya, proxy adalah bagian depan atau pembungkus yang meneruskan pemanggilan fungsi melalui fasilitas mereka sendiri (biasanya ke metode nyata) - berpotensi menambahkan beberapa fungsionalitas.

Proksi dinamis memungkinkan satu kelas tunggal dengan satu metode tunggal untuk melayani beberapa panggilan metode ke kelas arbitrer dengan jumlah metode yang berubah-ubah. Proksi dinamis dapat dianggap sebagai semacam Fasad , tetapi dapat dianggap sebagai implementasi antarmuka apa pun. Di bawah sampul, ini merutekan semua pemanggilan metode ke satu handler - metode invoke () .

Meskipun ini bukan alat yang dimaksudkan untuk tugas pemrograman sehari-hari, proxy dinamis bisa sangat berguna untuk penulis kerangka kerja. Ini juga dapat digunakan dalam kasus-kasus di mana implementasi kelas beton tidak akan diketahui sampai run-time.

Fitur ini dibangun ke dalam JDK standar, oleh karena itu tidak diperlukan ketergantungan tambahan.

2. Penangan Doa

Mari kita membangun proxy sederhana yang tidak benar-benar melakukan apa pun kecuali mencetak metode apa yang diminta untuk dipanggil dan mengembalikan nomor hard-code.

Pertama, kita perlu membuat subtipe java.lang.reflect.InvocationHandler :

public class DynamicInvocationHandler implements InvocationHandler { private static Logger LOGGER = LoggerFactory.getLogger( DynamicInvocationHandler.class); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { LOGGER.info("Invoked method: {}", method.getName()); return 42; } }

Di sini kita telah mendefinisikan proxy sederhana yang mencatat metode mana yang dipanggil dan mengembalikan 42.

3. Membuat Instance Proxy

Instance proxy yang dilayani oleh penangan permintaan yang baru saja kita tentukan dibuat melalui panggilan metode pabrik pada kelas java.lang.reflect.Proxy :

Map proxyInstance = (Map) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, new DynamicInvocationHandler());

Setelah kami memiliki instance proxy, kami dapat menjalankan metode antarmukanya seperti biasa:

proxyInstance.put("hello", "world");

Seperti yang diharapkan, pesan tentang metode put () yang dipanggil dicetak di file log.

4. Penangan Doa melalui Ekspresi Lambda

Karena InvocationHandler adalah antarmuka fungsional, dimungkinkan untuk mendefinisikan handler secara sebaris menggunakan ekspresi lambda:

Map proxyInstance = (Map) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, (proxy, method, methodArgs) -> { if (method.getName().equals("get")) { return 42; } else { throw new UnsupportedOperationException( "Unsupported method: " + method.getName()); } });

Di sini, kami mendefinisikan penangan yang mengembalikan 42 untuk semua operasi get dan melontarkan UnsupportedOperationException untuk yang lainnya.

Ini dipanggil dengan cara yang persis sama:

(int) proxyInstance.get("hello"); // 42 proxyInstance.put("hello", "world"); // exception

5. Contoh Timing Dynamic Proxy

Mari kita periksa satu skenario dunia nyata potensial untuk proxy dinamis.

Misalkan kita ingin merekam berapa lama fungsi kita dijalankan. Sejauh ini, pertama-tama kita menentukan penangan yang mampu menggabungkan objek "nyata", melacak informasi waktu dan permintaan reflektif:

public class TimingDynamicInvocationHandler implements InvocationHandler { private static Logger LOGGER = LoggerFactory.getLogger( TimingDynamicInvocationHandler.class); private final Map methods = new HashMap(); private Object target; public TimingDynamicInvocationHandler(Object target) { this.target = target; for(Method method: target.getClass().getDeclaredMethods()) { this.methods.put(method.getName(), method); } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.nanoTime(); Object result = methods.get(method.getName()).invoke(target, args); long elapsed = System.nanoTime() - start; LOGGER.info("Executing {} finished in {} ns", method.getName(), elapsed); return result; } }

Selanjutnya, proxy ini dapat digunakan pada berbagai tipe objek:

Map mapProxyInstance = (Map) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class }, new TimingDynamicInvocationHandler(new HashMap())); mapProxyInstance.put("hello", "world"); CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance( DynamicProxyTest.class.getClassLoader(), new Class[] { CharSequence.class }, new TimingDynamicInvocationHandler("Hello World")); csProxyInstance.length()

Di sini, kami telah membuat proksi peta dan urutan karakter (String).

Pemanggilan metode proxy akan mendelegasikan ke objek yang dibungkus serta menghasilkan pernyataan logging:

Executing put finished in 19153 ns Executing get finished in 8891 ns Executing charAt finished in 11152 ns Executing length finished in 10087 ns

6. Kesimpulan

Dalam tutorial singkat ini, kami telah memeriksa proxy dinamis Java serta beberapa kemungkinan penggunaannya.

Seperti biasa, kode dalam contoh dapat ditemukan di GitHub.