CodeGym /Blog Java /rawak /Proksi Dinamik di Jawa
John Squirrels
Tahap
San Francisco

Proksi Dinamik di Jawa

Diterbitkan dalam kumpulan
Hai! Hari ini kita akan mempertimbangkan topik yang agak penting dan menarik: penciptaan kelas proksi dinamik di Jawa. Ia tidak begitu mudah, jadi kami akan cuba memikirkannya menggunakan contoh :) Jadi, soalan yang paling penting: apakah proksi dinamik dan untuk apa ia? Kelas proksi ialah sejenis "tambahan" di atas kelas asal, yang membolehkan kami menukar gelagat kelas asal jika perlu. Apakah yang dimaksudkan dengan "mengubah tingkah laku" dan bagaimana ia berfungsi? Pertimbangkan contoh mudah. Katakan kita mempunyai antara muka Orang dan kelas Lelaki mudah yang melaksanakan antara muka ini

public interface Person {

   public void introduce(String name);
  
   public void sayAge(int age);
  
   public void sayWhereFrom(String city, String country);
}

public class Man implements Person {

   private String name;
   private int age;
   private String city;
   private String country;

   public Man(String name, int age, String city, String country) {
       this.name = name;
       this.age = age;
       this.city = city;
       this.country = country;
   }

   @Override
   public void introduce(String name) {

       System.out.println("My name is " + this.name);
   }

   @Override
   public void sayAge(int age) {
       System.out.println("I am " + this.age + " years old");
   }

   @Override
   public void sayWhereFrom(String city, String country) {

       System.out.println("I'm from " + this.city + ", " + this.country);
   }

   // ...getters, setters, etc.
}
Kelas Lelaki kami mempunyai 3 kaedah: perkenalkan, katakanAge, dan sayWhereFrom. Bayangkan bahawa kami mendapat kelas ini sebagai sebahagian daripada perpustakaan JAR di luar rak dan kami tidak boleh hanya menulis semula kodnya. Tetapi kita juga perlu mengubah tingkah lakunya. Sebagai contoh, kami tidak tahu kaedah mana yang mungkin dipanggil pada objek kami, tetapi kami mahu orang kami berkata "Hai!" (tiada siapa yang suka seseorang yang tidak sopan) apabila mana-mana kaedah dipanggil. Proksi dinamik - 2Apa yang perlu kita lakukan dalam keadaan ini? Kami memerlukan beberapa perkara:
  1. InvocationHandler

Apakah ini? InvocationHandler ialah antara muka khas yang membolehkan kami memintas sebarang panggilan kaedah ke objek kami dan menambah gelagat tambahan yang kami perlukan. Kita perlu mencipta pemintas kita sendiri, iaitu mencipta kelas yang melaksanakan antara muka ini. Ini agak mudah:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PersonInvocationHandler implements InvocationHandler {
  
private Person person;

public PersonInvocationHandler(Person person) {
   this.person = person;
}

 @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

       System.out.println("Hi!");
       return null;
   }
}
Kita perlu melaksanakan hanya satu kaedah antara muka: invoke() . Dan, dengan cara ini, ia melakukan apa yang kita perlukan: ia memintas semua panggilan kaedah ke objek kami dan menambah tingkah laku yang diperlukan (di dalam kaedah invoke() , kami mengeluarkan "Hai!" ke konsol).
  1. Objek asal dan proksinya.
Kami mencipta objek Man asal kami dan "tambahan" (proksi) untuknya:

import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       // Create the original object
       Man arnold = new Man("Arnold", 30, "Thal", "Austria");

       // Get the class loader from the original object
       ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();

       // Get all the interfaces that the original object implements
       Class[] interfaces = arnold.getClass().getInterfaces();

       // Create a proxy for our arnold object
       Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));

       // Call one of our original object's methods on the proxy object
       proxyArnold.introduce(arnold.getName());

   }
}
Ini tidak kelihatan sangat mudah! Saya secara khusus menambah komen untuk setiap baris kod. Mari kita lihat dengan lebih dekat apa yang berlaku. Dalam baris pertama, kita hanya membuat objek asal yang mana kita akan membuat proksi. Dua baris berikut boleh menyebabkan anda mengalami kesukaran:

 // Get the class loader from the original object
ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();

// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();
Sebenarnya, tiada apa-apa yang istimewa berlaku di sini :) Dalam baris keempat, kami menggunakan kelas Proksi khas dan kaedah newProxyInstance() statiknya :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Kaedah ini hanya mencipta objek proksi kami. Kami menyampaikan kepada kaedah maklumat tentang kelas asal, yang kami terima dalam langkah terakhir ( ClassLoader dan senarai antara mukanya), serta objek InvocationHandler yang dibuat sebelum ini . Perkara utama ialah jangan lupa untuk menyerahkan objek arnold asal kami kepada pengendali invocation, jika tidak, tiada apa-apa untuk "mengendalikan" :) Apa yang kami hadapi? Kami kini mempunyai objek proksi: proxyArnold . Ia boleh memanggil mana-mana kaedah antara muka Orang . kenapa? Kerana kami memberikannya senarai semua antara muka di sini:

// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Kini ia mengetahui tentang semua kaedah antara muka Orang . Di samping itu, kami menyerahkan kepada proksi kami objek PersonInvocationHandler yang dikonfigurasikan untuk berfungsi dengan objek arnold :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Sekarang jika kami memanggil mana-mana kaedah antara muka Orang pada objek proksi, pengendali kami memintas panggilan dan melaksanakan kaedah invoke() nya sendiri . Mari cuba jalankan kaedah main() ! Output konsol:

Hi!
Cemerlang! Kami melihat bahawa bukannya kaedah Person.introduce() asal , kaedah invoke() PersonInvocationHandler() kami dipanggil:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   System.out.println("Hi!");
   return null;
}
"Hai!" dipaparkan pada konsol, tetapi ini bukan kelakuan yang kami mahukan :/ Apa yang kami cuba capai ialah pertama kali memaparkan "Hai!" dan kemudian panggil kaedah asal itu sendiri.Dengan kata lain, panggilan kaedah

proxyArnold.introduce(arnold.getName());
mesti memaparkan "Hai! Nama saya Arnold", bukan sekadar "Hai!" Bagaimanakah kita boleh mencapai ini? Ia tidak rumit: kita hanya perlu mengambil sedikit kebebasan dengan pengendali kita dan kaedah invoke() :) Beri perhatian kepada hujah yang dihantar kepada kaedah ini:

public Object invoke(Object proxy, Method method, Object[] args)
Kaedah invoke () mempunyai akses kepada kaedah asal yang digunakan dan kepada semua hujahnya (Kaedah kaedah, Object[] args). Dalam erti kata lain, jika kita memanggil kaedah proxyArnold.introduce(arnold.getName()) supaya kaedah invoke() dipanggil dan bukannya kaedah introduce() , maka di dalam kaedah ini kita mempunyai akses kepada kaedah introduce() asal dan hujahnya! Akibatnya, kita boleh melakukan ini:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PersonInvocationHandler implements InvocationHandler {

   private Person person;

   public PersonInvocationHandler(Person person) {

       this.person = person;
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       System.out.println("Hi!");
       return method.invoke(person, args);
   }
}
Sekarang dalam kaedah invoke() kami telah menambah panggilan kepada kaedah asal. Jika kita kini cuba menjalankan kod dari contoh sebelumnya:

import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       // Create the original object
       Man arnold = new Man("Arnold", 30, "Thal", "Austria");

       // Get the class loader from the original object
       ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();

       // Get all the interfaces that the original object implements
       Class[] interfaces = arnold.getClass().getInterfaces();

       // Create a proxy for our arnold object
       Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));

       // Call one of our original object's methods on the proxy object
       proxyArnold.introduce(arnold.getName());
   }
}
maka kita akan melihat bahawa sekarang semuanya berfungsi sebagaimana mestinya :) Output konsol:

Hi! My name is Arnold
Bilakah anda mungkin memerlukan ini? Sebenarnya, agak kerap. Corak reka bentuk "proksi dinamik" digunakan secara aktif dalam teknologi popular... Oh, kebetulan, saya terlupa untuk menyatakan bahawa Proksi Dinamik ialah corak reka bentuk! Tahniah, anda belajar satu lagi! :) Proksi dinamik - 3Sebagai contoh, ia digunakan secara aktif dalam teknologi dan rangka kerja popular yang berkaitan dengan keselamatan. Bayangkan anda mempunyai 20 kaedah yang hanya boleh dilaksanakan oleh pengguna yang log masuk ke program anda. Menggunakan teknik yang telah anda pelajari, anda boleh dengan mudah menambah kepada 20 kaedah ini semakan untuk melihat sama ada pengguna telah memasukkan bukti kelayakan yang sah tanpa menduplikasi kod pengesahan dalam setiap kaedah. Atau katakan anda ingin membuat log di mana semua tindakan pengguna akan direkodkan. Ini juga mudah dilakukan menggunakan proksi. Malah sekarang, anda hanya boleh menambah kod pada contoh kami di atas supaya nama kaedah dipaparkan apabila anda memanggil invoke() , dan itu akan menghasilkan log yang sangat mudah bagi program kami :) Kesimpulannya, perhatikan satu had penting. Objek proksi berfungsi dengan antara muka, bukan kelas. Proksi dicipta untuk antara muka. Lihat kod ini:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Di sini kami mencipta proksi khusus untuk antara muka Orang . Jika kita cuba mencipta proksi untuk kelas, iaitu menukar jenis rujukan dan cuba menghantar ke kelas Man , ia tidak akan berfungsi.

Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));

proxyArnold.introduce(arnold.getName());
Pengecualian dalam benang "utama" java.lang.ClassCastException: com.sun.proxy.$Proxy0 tidak boleh dihantar ke Man Mempunyai antara muka adalah keperluan mutlak. Proksi berfungsi dengan antara muka. Itu sahaja untuk hari ini :) Nah, sekarang adalah baik untuk menyelesaikan beberapa tugasan! :) Sehingga lain kali!
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION