JavaRush /Blog Java /Random-MS /Proksi Dinamik di Jawa

Proksi Dinamik di Jawa

Diterbitkan dalam kumpulan
hello! Hari ini kita akan melihat topik yang agak penting dan menarik - mencipta kelas proksi dinamik di Jawa. Ia tidak terlalu mudah, jadi mari kita cuba memikirkannya dengan contoh :) Jadi, soalan yang paling penting: apakah proksi dinamik dan untuk apa ia? Kelas proksi ialah sejenis "struktur besar" berbanding kelas asal, yang membolehkan kami mengubah tingkah lakunya jika perlu. Apakah yang dimaksudkan dengan mengubah tingkah laku dan bagaimana ia berfungsi? Mari kita lihat contoh mudah. Katakan kita mempunyai antara muka Persondan kelas mudah Manyang melaksanakan antara muka ini
public interface Person {

   public void introduce(String name);

   public void sayAge(int age);

   public void sayFrom(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("Меня зовут " + this.name);
   }

   @Override
   public void sayAge(int age) {
       System.out.println("Мне " + this.age + " years");
   }

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

       System.out.println("Я из города " + this.city + ", " + this.country);
   }

   //..геттеры, сеттеры, и т.д.
}
Kelas kami Manmempunyai 3 kaedah: perkenalkan diri anda, sebut umur anda dan nyatakan dari mana anda berasal. Mari bayangkan bahawa kami menerima kelas ini sebagai sebahagian daripada perpustakaan JAR siap dan tidak boleh mengambil dan menulis semula kodnya. Namun, kita perlu mengubah tingkah lakunya. Sebagai contoh, kami tidak tahu kaedah mana yang akan dipanggil pada objek kami, dan oleh itu kami mahu orang itu terlebih dahulu berkata "Hello!" apabila memanggil mana-mana daripada mereka. (tiada siapa suka seseorang yang tidak sopan). Proksi dinamik - 1Apa yang patut kita lakukan dalam keadaan sedemikian? Kami akan memerlukan beberapa perkara:
  1. InvocationHandler

Apa ini? Ia boleh diterjemahkan secara literal sebagai "pemintas panggilan". Ini menerangkan tujuannya dengan agak tepat. InvocationHandlerialah antara muka khas yang membolehkan kami memintas sebarang panggilan kaedah ke objek kami dan menambah tingkah laku tambahan yang kami perlukan. Kita perlu membuat pemintas kita sendiri - iaitu mencipta kelas dan melaksanakan antara muka ini. Ia 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("Hello!");
       return null;
   }
}
Kita perlu melaksanakan hanya satu kaedah antara muka - invoke(). Ia, sebenarnya, melakukan apa yang kita perlukan - ia memintas semua panggilan kaedah ke objek kita dan menambah tingkah laku yang diperlukan (di sini kita invoke()mencetak "Hello!" ke konsol di dalam kaedah).
  1. Objek asal dan proksinya.
Mari kita buat objek asal Mandan "superstruktur" (proksi) untuknya:
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       //Создаем оригинальный an object
       Man vasia = new Man("Vasya", 30, "Санкт-Петербург", "Россия");

       //Получаем загрузчик класса у оригинального an object
       ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

       //Получаем все интерфейсы, которые реализует оригинальный an object
       Class[] interfaces = vasia.getClass().getInterfaces();

       //Создаем прокси нашего an object vasia
       Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

       //Вызываем у прокси an object один из методов нашего оригинального an object
       proxyVasia.introduce(vasia.getName());

   }
}
Nampak tak simple sangat! Saya secara khusus menulis ulasan untuk setiap baris kod: mari kita lihat dengan lebih dekat apa yang berlaku di sana.

Dalam baris pertama kita hanya membuat objek asal yang mana kita akan membuat proksi. Dua baris berikut boleh menyebabkan kekeliruan anda:
//Получаем загрузчик класса у оригинального an object
ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();
Tetapi sebenarnya tiada apa-apa yang istimewa berlaku di sini :) Untuk mencipta proksi, kita memerlukan ClassLoader(pemuat kelas) objek asal dan senarai semua antara muka yang kelas asal kita (iaitu Man) laksanakan. Jika anda tidak tahu apa itu ClassLoader, anda boleh membaca artikel ini tentang memuatkan kelas ke dalam JVM atau yang ini di Habré , tetapi jangan terlalu risau dengannya lagi. Ingatlah bahawa kami mendapat sedikit maklumat tambahan yang kemudiannya kami perlukan untuk mencipta objek proksi. Pada baris keempat kami menggunakan kelas khas Proxydan kaedah statiknya newProxyInstance():
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Kaedah ini hanya mencipta objek proksi kami. Kepada kaedah kami menyampaikan maklumat tentang kelas asal yang kami terima pada langkah sebelumnya (ia ClassLoaderdan senarai antara mukanya), serta objek pemintas yang kami buat sebelum ini - InvocationHandler'a. Perkara utama ialah jangan lupa untuk menghantar objek asal kami kepada pemintas vasia, jika tidak, ia tidak akan mempunyai apa-apa untuk "memintas" :) Apa yang kami hadapi? Kami kini mempunyai objek proksi vasiaProxy. Ia boleh memanggil mana-mana kaedah antara mukaPerson . kenapa? Kerana kami memberikan senarai semua antara muka - di sini:
//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();

//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Kini dia "sedar" tentang semua kaedah antara muka Person. Di samping itu, kami memberikan proksi kami objek PersonInvocationHandleryang dikonfigurasikan untuk berfungsi dengan objek vasia:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Sekarang, jika kami memanggil sebarang kaedah antara muka pada objek proksi Person, pemintas kami akan "menangkap" panggilan ini dan sebaliknya melaksanakan kaedahnya sendiri invoke(). Mari cuba jalankan kaedahnya main()! Output konsol: Hello! Hebat! Kami melihat bahawa bukannya kaedah sebenar, kaedah kami Person.introduce()dipanggil : invoke()PersonInvocationHandler()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   System.out.println("Hello!");
   return null;
}
Dan konsol itu memaparkan "Hello!" Tetapi ini bukan tingkah laku yang kami ingin dapatkan :/ Menurut idea kami, “Hello!” harus dipaparkan dahulu, dan kemudian kaedah itu sendiri yang kami panggil harus berfungsi. Dengan kata lain, kaedah ini memanggil:
proxyVasia.introduce(vasia.getName());
harus mengeluarkan ke konsol "Hello! Nama saya Vasya," dan bukan hanya "Hello!" Bagaimanakah kita boleh mencapai ini? Tiada apa-apa yang rumit: anda hanya perlu bermain-main sedikit dengan pemintas dan kaedah kami 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 yang dipanggil sebaliknya dan semua hujahnya (Kaedah kaedah, Object[] args). Dalam erti kata lain, jika kita memanggil kaedah proxyVasia.introduce(vasia.getName()), dan bukannya kaedah , introduce()kaedah dipanggil invoke(), di dalam kaedah itu kita mempunyai akses kepada kaedah asal introduce()dan hujahnya! Akibatnya, kita boleh melakukan sesuatu seperti 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("Hello!");
       return method.invoke(person, args);
   }
}
Kini kami telah menambah invoke()panggilan kepada kaedah asal kepada kaedah tersebut. Jika kita kini cuba menjalankan kod dari contoh sebelumnya:
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       //Создаем оригинальный an object
       Man vasia = new Man("Vasya", 30, "Санкт-Петербург", "Россия");

       //Получаем загрузчик класса у оригинального an object
       ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

       //Получаем все интерфейсы, которые реализует оригинальный an object
       Class[] interfaces = vasia.getClass().getInterfaces();

       //Создаем прокси нашего an object vasia
       Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

       //Вызываем у прокси an object один из методов нашего оригинального an object
       proxyVasia.introduce(vasia.getName());
   }
}
maka kita akan melihat bahawa kini semuanya berfungsi sebagaimana mestinya :) Output konsol: Hello! Nama saya Vasya. Di manakah anda memerlukannya? Sebenarnya banyak tempat. Corak reka bentuk "proksi dinamik" digunakan secara aktif dalam teknologi popular... dan dengan cara itu, saya terlupa untuk memberitahu anda bahawa Dynamic Proxyia adalah corak ! Tahniah, anda telah belajar satu lagi! :) Proksi dinamik - 2Jadi, 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 log masuk program anda. Dengan menggunakan teknik yang telah anda pelajari, anda boleh dengan mudah menambah kepada 20 kaedah ini semakan untuk melihat sama ada pengguna telah memasukkan log masuk dan kata laluan, tanpa menduplikasi kod pengesahan secara berasingan dalam setiap kaedah. Atau, sebagai contoh, jika anda ingin membuat log di mana semua tindakan pengguna akan direkodkan, ini juga mudah dilakukan menggunakan proksi. Anda boleh sekarang: cuma tambah kod pada contoh supaya nama kaedah dipaparkan dalam konsol apabila dipanggil invoke(), dan anda akan mendapat log ringkas program kami :) Pada akhir kuliah, beri perhatian kepada satu perkara penting had . Mencipta objek proksi berlaku pada peringkat antara muka, bukan peringkat kelas. Proksi dicipta untuk antara muka. Lihat kod ini:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Di sini kami mencipta proksi khusus untuk antara muka Person. Jika kami cuba mencipta proksi untuk kelas, iaitu, kami menukar jenis pautan dan cuba menghantar ke kelas Man, tiada apa yang akan berfungsi.
Man proxyVasia = (Man) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

proxyVasia.introduce(vasia.getName());
Pengecualian dalam benang "utama" java.lang.ClassCastException: com.sun.proxy.$Proxy0 tidak boleh dihantar ke Man Mempunyai antara muka adalah satu keperluan. Proksi berfungsi pada peringkat antara muka. Itu sahaja untuk hari ini :) Sebagai bahan tambahan mengenai topik proksi, saya boleh mengesyorkan anda video yang sangat baik dan juga artikel yang bagus . Nah, sekarang adalah baik untuk menyelesaikan beberapa masalah! :) Jumpa anda!
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION