Proksi Dinamik di Jawa

1. Pengenalan

Artikel ini adalah mengenai proksi dinamik Java - yang merupakan salah satu mekanisme proksi utama yang tersedia untuk kita dalam bahasa.

Sederhananya, proksi adalah bahagian depan atau pembungkus yang melewati pemanggilan fungsi melalui kemudahan mereka sendiri (biasanya menggunakan kaedah sebenar) - berpotensi menambahkan beberapa fungsi.

Proksi dinamik membolehkan satu kelas tunggal dengan satu kaedah untuk melayani panggilan kaedah berbilang ke kelas sewenang-wenang dengan bilangan kaedah sewenang-wenangnya. Proksi dinamik boleh dianggap sebagai semacam Fasad , tetapi proksi yang boleh berpura-pura sebagai pelaksanaan antara muka apa pun. Di bawah penutup, ia mengarahkan semua kaedah pemanggilan ke pengendali tunggal - kaedah invoke () .

Walaupun bukan alat yang dimaksudkan untuk tugas pengaturcaraan sehari-hari, proksi dinamik dapat sangat berguna untuk penulis kerangka. Ia juga dapat digunakan dalam kasus-kasus di mana pelaksanaan kelas konkrit tidak akan diketahui sampai waktu berjalan.

Ciri ini dimasukkan ke dalam JDK standard, oleh itu tidak diperlukan pergantungan tambahan.

2. Pengendali Pemohon

Marilah kita membina proksi mudah yang sebenarnya tidak melakukan apa-apa kecuali mencetak kaedah apa yang diminta untuk dipanggil dan mengembalikan nombor yang dikodkan.

Pertama, kita perlu membuat subjenis 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 kami telah menentukan proksi ringkas yang mencatat kaedah mana yang dipanggil dan mengembalikan 42.

3. Membuat Proxy Instance

Contoh proksi yang dilayan oleh pengendali permintaan yang baru sahaja kami tetapkan dibuat melalui panggilan kaedah kilang di kelas java.lang.reflect.Proxy :

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

Sebaik sahaja kita mempunyai contoh proksi, kita boleh menggunakan kaedah antara muka seperti biasa:

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

Seperti yang diharapkan, mesej mengenai kaedah put () yang digunakan dicetak dalam fail log.

4. Pengendali Permintaan melalui Lambda Expressions

Oleh kerana InvocationHandler adalah antara muka yang berfungsi, adalah mungkin untuk menentukan pengendali sebaris menggunakan ungkapan 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 menentukan pengendali yang mengembalikan 42 untuk semua operasi get dan melemparkan UnsupportedOperationException untuk semua yang lain.

Ia dipanggil dengan cara yang sama:

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

5. Contoh Proksi Dinamik Masa

Mari kaji satu senario dunia nyata yang berpotensi untuk proksi dinamik.

Anggaplah kita ingin merakam berapa lama masa fungsi kita dilaksanakan. Sejauh ini, kami pertama kali menentukan pengendali yang mampu membungkus objek "nyata", mengesan maklumat masa dan pemanggilan 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; } }

Selepas itu, proksi ini dapat digunakan pada pelbagai jenis 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 memberikan proksi peta dan urutan char (String).

Permintaan kaedah proksi akan diberikan kepada objek yang dibungkus dan juga menghasilkan pernyataan pembalakan:

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. Kesimpulannya

Dalam tutorial ringkas ini, kami telah memeriksa proksi dinamik Java serta beberapa kemungkinan penggunaannya.

Seperti biasa, kod dalam contoh boleh didapati di GitHub.