CodeGym /Java Blog /Acak /Proksi Dinamis di Jawa
John Squirrels
Level 41
San Francisco

Proksi Dinamis di Jawa

Dipublikasikan di grup Acak
Hai! Hari ini kita akan mempertimbangkan topik yang agak penting dan menarik: pembuatan kelas proxy dinamis di Jawa. Ini tidak terlalu sederhana, jadi kami akan mencoba mencari tahu menggunakan contoh :) Jadi, pertanyaan terpenting: apa itu proxy dinamis dan untuk apa? Kelas proxy adalah sejenis "tambahan" di atas kelas asli, yang memungkinkan kita untuk mengubah perilaku kelas asli jika perlu. Apa artinya "mengubah perilaku" dan bagaimana cara kerjanya? Pertimbangkan contoh sederhana. Misalkan kita memiliki antarmuka Person dan kelas Man sederhana yang mengimplementasikan antarmuka 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 Man kita memiliki 3 metode: perkenalkan, sayAge, dan sayWhereFrom. Bayangkan kita mendapatkan kelas ini sebagai bagian dari perpustakaan JAR siap pakai dan kita tidak bisa begitu saja menulis ulang kodenya. Tapi kita juga perlu mengubah perilakunya. Misalnya, kita tidak tahu metode mana yang mungkin dipanggil pada objek kita, tetapi kita ingin orang kita mengatakan "Hai!" (tidak ada yang menyukai seseorang yang tidak sopan) ketika salah satu metode dipanggil. Proxy dinamis - 2Apa yang harus kita lakukan dalam situasi ini? Kami membutuhkan beberapa hal:
  1. PemanggilanHandler

Apa ini? InvocationHandler adalah antarmuka khusus yang memungkinkan kita mencegat panggilan metode apa pun ke objek kita dan menambahkan perilaku tambahan yang kita perlukan. Kita perlu membuat pencegat kita sendiri, yaitu membuat kelas yang mengimplementasikan interface ini. Ini cukup sederhana:

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 hanya perlu mengimplementasikan satu metode antarmuka: invoke() . Dan, omong-omong, ia melakukan apa yang kita perlukan: ia memotong semua pemanggilan metode ke objek kita dan menambahkan perilaku yang diperlukan (di dalam metode invoke() , kita mengeluarkan "Hai!" ke konsol).
  1. Objek asli dan proxy-nya.
Kami membuat objek Man asli kami dan "add-on" (proxy) 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 terlihat sangat sederhana! Saya secara khusus menambahkan komentar untuk setiap baris kode. Mari kita lihat lebih dekat apa yang terjadi. Di baris pertama, kita cukup membuat objek asli yang akan kita buat proxy-nya. Dua baris berikut dapat menyebabkan Anda kesulitan:

 // 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, tidak ada yang istimewa terjadi di sini :) Di baris keempat, kami menggunakan kelas Proxy khusus dan metode statis newProxyInstance() :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Metode ini hanya membuat objek proxy kami. Kami meneruskan ke metode informasi tentang kelas asli, yang kami terima di langkah terakhir ( ClassLoader dan daftar antarmukanya), serta objek InvocationHandler yang dibuat sebelumnya . Hal utama adalah jangan lupa untuk meneruskan objek arnold asli kita ke penangan doa, jika tidak, tidak akan ada yang "ditangani" :) Apa yang kita dapatkan? Kami sekarang memiliki objek proxy: proxyArnold . Itu dapat memanggil metode apa pun dari antarmuka Person . Mengapa? Karena kami memberikannya daftar semua antarmuka 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));
Sekarang ia tahu tentang semua metode antarmuka Person . Selain itu, kami meneruskan ke proxy kami objek PersonInvocationHandler yang dikonfigurasi untuk bekerja dengan objek arnold :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Sekarang jika kita memanggil metode apa pun dari antarmuka Person pada objek proxy, penangan kita mencegat panggilan dan menjalankan metode invoke() miliknya sendiri . Mari kita coba jalankan metode main() ! Keluaran konsol:

Hi!
Bagus sekali! Kita melihat bahwa alih-alih metode Person.introduce() asli , metode invoke() dari PersonInvocationHandler() kita disebut:

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

   System.out.println("Hi!");
   return null;
}
"Hai!" ditampilkan di konsol, tetapi ini bukan perilaku yang kami inginkan :/ Yang ingin kami capai adalah menampilkan "Hai!" dan kemudian memanggil metode asli itu sendiri Dengan kata lain, panggilan metode

proxyArnold.introduce(arnold.getName());
harus menampilkan "Hai! Nama saya Arnold", bukan hanya "Hai!" Bagaimana kita bisa mencapai hal ini? Ini tidak rumit: kita hanya perlu mengambil kebebasan dengan handler kita dan metode invoke() :) Perhatikan argumen apa yang diteruskan ke metode ini:

public Object invoke(Object proxy, Method method, Object[] args)
Metode invoke() memiliki akses ke metode yang awalnya dipanggil, dan ke semua argumennya (metode Metode, Object[] args). Dengan kata lain, jika kita memanggil metode proxyArnold.introduce(arnold.getName()) sehingga metode invoke() dipanggil alih-alih metode perkenalkan() , maka di dalam metode ini kita memiliki akses ke metode perkenalkan() asli dan argumentasinya! Hasilnya, kita dapat 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 metode invoke() kami telah menambahkan panggilan ke metode asli. Jika sekarang kami mencoba menjalankan kode dari contoh kami 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 bahwa sekarang semuanya berfungsi sebagaimana mestinya :) Keluaran konsol:

Hi! My name is Arnold
Kapan Anda membutuhkan ini? Sebenarnya cukup sering. Pola desain "proksi dinamis" secara aktif digunakan dalam teknologi populer... Omong-omong, saya lupa menyebutkan bahwa Proksi Dinamis adalah pola desain! Selamat, Anda belajar satu lagi! :) Proksi dinamis - 3Misalnya, ini digunakan secara aktif dalam teknologi dan kerangka kerja populer yang terkait dengan keamanan. Bayangkan Anda memiliki 20 metode yang seharusnya hanya dijalankan oleh pengguna yang masuk ke program Anda. Dengan menggunakan teknik yang telah Anda pelajari, Anda dapat dengan mudah menambahkan pemeriksaan ke 20 metode ini untuk melihat apakah pengguna telah memasukkan kredensial yang valid tanpa menduplikasi kode verifikasi di setiap metode. Atau misalkan Anda ingin membuat log tempat semua tindakan pengguna akan direkam. Ini juga mudah dilakukan dengan menggunakan proxy. Bahkan sekarang, Anda cukup menambahkan kode ke contoh kami di atas sehingga nama metode ditampilkan saat Anda memanggil invoke() , dan itu akan menghasilkan log yang sangat sederhana dari program kami :) Kesimpulannya, perhatikan satu batasan penting. Objek proxy bekerja dengan antarmuka, bukan kelas. Proksi dibuat untuk antarmuka. Lihatlah kode ini:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Disini kita membuat proxy khusus untuk interface Person . Jika kami mencoba membuat proxy untuk kelas tersebut, yaitu mengubah jenis referensi dan mencoba mentransmisikan ke kelas Man , itu tidak akan berhasil.

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

proxyArnold.introduce(arnold.getName());
Pengecualian di utas "utama" java.lang.ClassCastException: com.sun.proxy.$Proxy0 tidak dapat dilemparkan ke Man Memiliki antarmuka adalah persyaratan mutlak. Proxy bekerja dengan antarmuka. Itu saja untuk hari ini :) Nah, sekarang akan baik untuk menyelesaikan beberapa tugas! :) Sampai Lain waktu!
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION