Jelajahi Dynamic Proxy API

Dengan diperkenalkannya Dynamic Proxy API di Java 1.3, peningkatan besar dan sering diabaikan telah dibuat pada platform Java. Penggunaan proxy dinamis terkadang merupakan konsep yang sulit dipahami. Pada artikel ini, saya berharap untuk memperkenalkan Anda terlebih dahulu ke pola desain Proxy dan kemudian ke java.lang.reflect.Proxykelas dan java.lang.reflect.InvocationHandlerantarmuka, yang merupakan inti dari fungsionalitas Dynamic Proxy.

Fungsionalitas yang dibahas di sini menggabungkan proxy dinamis Java 1.3 dengan tipe data abstrak untuk menghadirkan pengetikan yang kuat pada tipe tersebut. Saya juga akan membahas kekuatan proxy dinamis dengan memperkenalkan konsep views dalam pemrograman Java Anda. Terakhir, saya akan memperkenalkan cara yang ampuh untuk menambahkan kontrol akses ke objek Java Anda dengan, tentu saja, menggunakan proxy dinamis.

Definisi proxy

Sebuah proxy memaksa panggilan metode objek terjadi secara tidak langsung melalui objek proxy, yang bertindak sebagai pengganti atau delegasi untuk objek yang mendasari yang sedang di-proxy. Objek proxy biasanya dideklarasikan sehingga objek klien tidak memiliki indikasi bahwa mereka memiliki instance objek proxy.

Beberapa proxy umum adalah proxy akses, fasad, proxy jarak jauh, dan proxy virtual. Proksi akses digunakan untuk menegakkan kebijakan keamanan pada akses ke layanan atau objek penyedia data. Fasad adalah satu antarmuka ke beberapa objek yang mendasari. Proksi jarak jauh digunakan untuk menutupi atau melindungi objek klien dari fakta bahwa objek yang mendasarinya adalah jarak jauh. Proksi virtual digunakan untuk melakukan pembuatan instance lazy atau just-in-time dari objek nyata.

Proksi adalah pola desain dasar yang cukup sering digunakan dalam pemrograman. Namun, salah satu kekurangannya adalah kekhususan atau kopling yang erat dari proxy dengan objek yang mendasarinya. Melihat UML untuk pola desain Proxy pada Gambar 1, Anda melihat bahwa agar proxy berguna dan transparan, biasanya perlu mengimplementasikan antarmuka atau mewarisi dari superclass yang dikenal (dengan pengecualian fasad, mungkin).

Proksi dinamis

Di Java 1.3, Sun memperkenalkan Dynamic Proxy API. Agar proxy dinamis berfungsi, Anda harus memiliki antarmuka proxy terlebih dahulu. Antarmuka proxy adalah antarmuka yang diimplementasikan oleh kelas proxy. Kedua, Anda memerlukan instance kelas proxy.

Menariknya, Anda dapat memiliki kelas proxy yang mengimplementasikan banyak antarmuka. Namun, ada beberapa batasan pada antarmuka yang Anda terapkan. Penting untuk mengingat batasan tersebut saat membuat proxy dinamis Anda:

  1. Antarmuka proxy harus berupa antarmuka. Dengan kata lain, itu tidak bisa menjadi kelas (atau kelas abstrak) atau primitif.
  2. Larik antarmuka yang diteruskan ke konstruktor proxy tidak boleh berisi duplikat dari antarmuka yang sama. Sun menetapkan itu, dan masuk akal jika Anda tidak akan mencoba mengimplementasikan antarmuka yang sama dua kali pada waktu yang sama. Misalnya, sebuah array { IPerson.class, IPerson.class }akan menjadi ilegal, tetapi kodenya { IPerson.class, IEmployee.class }tidak. Kode yang memanggil konstruktor harus memeriksa kasus itu dan menyaring duplikat.
  3. Semua antarmuka harus terlihat dengan yang ClassLoaderditentukan selama panggilan konstruksi. Sekali lagi, itu masuk akal. The ClassLoaderharus mampu memuat antarmuka untuk proxy.
  4. Semua antarmuka nonpublik harus dari paket yang sama. Anda tidak dapat memiliki antarmuka pribadi dari paket com.xyzdan kelas proxy dalam paket com.abc. Jika Anda memikirkannya, itu sama seperti saat memprogram kelas Java biasa. Anda juga tidak dapat mengimplementasikan antarmuka nonpublik dari paket lain dengan kelas reguler.
  5. Antarmuka proxy tidak boleh memiliki konflik metode. Anda tidak dapat memiliki dua metode yang mengambil parameter yang sama tetapi mengembalikan jenis yang berbeda. Misalnya, metode public void foo()dan public String foo()tidak dapat didefinisikan di kelas yang sama karena mereka memiliki tanda tangan yang sama, tetapi mengembalikan tipe yang berbeda (lihat Spesifikasi Bahasa Java ). Sekali lagi, itu sama untuk kelas reguler.
  6. Kelas proxy yang dihasilkan tidak dapat melebihi batas VM, seperti batasan jumlah antarmuka yang dapat diimplementasikan.

Untuk membuat kelas proxy dinamis yang sebenarnya, yang perlu Anda lakukan adalah mengimplementasikan java.lang.reflect.InvocationHandlerantarmuka:

Kelas publik MyDynamicProxyClass mengimplementasikan java.lang.reflect.InvocationHandler {Object obj; public MyDynamicProxyClass (Object obj) {this.obj = obj; } pemanggilan Objek publik (proxy Objek, Metode m, Objek [] args) melempar Throwable {coba {// lakukan sesuatu} menangkap (InvocationTargetException e) {throw e.getTargetException (); } catch (Exception e) {throw e; } // kembalikan sesuatu}}

Hanya itu yang ada untuk itu! Betulkah! Saya tidak berbohong! Oke, nah, Anda juga harus memiliki antarmuka proxy Anda yang sebenarnya:

antarmuka publik MyProxyInterface {public Object MyMethod (); }

Kemudian untuk benar-benar menggunakan proxy dinamis tersebut, kodenya terlihat seperti ini:

MyProxyInterface foo = (MyProxyInterface) java.lang.reflect.Proxy.newProxyInstance (obj.getClass (). GetClassLoader (), Kelas [] {MyProxyInterface.class}, MyDynamicProxyClass (obj)); 

Mengetahui bahwa kode di atas sangat jelek, saya ingin menyembunyikannya dalam beberapa jenis metode pabrik. Jadi alih-alih memiliki kode yang berantakan di kode klien, saya akan menambahkan metode itu ke saya MyDynamicProxyClass:

Objek publik statis newInstance (Object obj, Class [] antarmuka) {return java.lang.reflect.Proxy.newProxyInstance (obj.getClass (). getClassLoader (), antarmuka, MyDynamicProxyClass (obj)); }

Itu memungkinkan saya untuk menggunakan kode klien berikut sebagai gantinya:

MyProxyInterface foo = (MyProxyInterface) MyDynamicProxyClass.newInstance (obj, Kelas baru [] {MyProxyInterface.class}); 

Itu adalah kode yang jauh lebih bersih. Mungkin ada baiknya di masa mendatang untuk memiliki kelas pabrik yang sepenuhnya menyembunyikan seluruh kode dari klien, sehingga kode klien lebih terlihat seperti:

MyProxyInterface foo = Builder.newProxyInterface (); 

Secara keseluruhan, menerapkan proxy dinamis cukup sederhana. Namun, di balik kesederhanaan itu ada kekuatan besar. Kekuatan hebat itu berasal dari fakta bahwa proxy dinamis Anda dapat mengimplementasikan antarmuka atau grup antarmuka apa pun. Saya akan mengeksplorasi konsep itu di bagian selanjutnya.

Data abstrak

Contoh terbaik dari data abstrak ada di kelas kumpulan Java seperti

java.util.ArrayList

,

java.util.HashMap

, atau

java.util.Vector

. Kelas koleksi tersebut mampu menampung objek Java apa pun. Mereka sangat berharga dalam penggunaannya di Jawa. Konsep tipe data abstrak adalah konsep yang kuat, dan kelas-kelas itu membawa kekuatan koleksi ke tipe data apa pun.

Mengikat keduanya

Dengan menggabungkan konsep proxy dinamis dengan tipe data abstrak, Anda bisa mendapatkan semua manfaat tipe data abstrak dengan pengetikan yang kuat. Selain itu, Anda dapat dengan mudah menggunakan kelas proxy untuk menerapkan kontrol akses, proxy virtual, atau jenis proxy lain yang berguna. Dengan menutupi pembuatan dan penggunaan proxy yang sebenarnya dari kode klien, Anda dapat membuat perubahan pada kode proxy yang mendasarinya tanpa memengaruhi kode klien.

Konsep pandangan

Saat merancang program Java, sering terjadi masalah desain di mana kelas harus menampilkan banyak antarmuka berbeda ke kode klien. Ambil Gambar 2 misalnya:

public class Person { private String name; private String address; private String phoneNumber; public String getName() { return name; } public String getAddress() { return address; } public String getPhoneNumber() { return phoneNumber; } public void setName(String name) { this.name = name; } public void setAddress(String address) { this.address = address; } public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } } public class Employee extends Person { private String SSN; private String department; private float salary; public String getSSN() { return ssn; } public String getDepartment() { return department; } public float getSalary() { return salary; } public void setSSN(String ssn) { this.ssn = ssn; } public void setDepartment(String department) { this.department = department; } public void setSalary(float salary) { this.salary = salary; } } public class Manager extends Employee { String title; String[] departments; public String getTitle() { return title; } public String[] getDepartments() { return departments; } public void setTitle(String title) { this.title = title; } public void setDepartments(String[] departments) { this.departments = departments; } } 

In that example, a Person class contains the properties Name, Address, and PhoneNumber. Then, there is the Employee class, which is a Person subclass and contains the additional properties SSN, Department, and Salary. From the Employee class, you have the subclass Manager, which adds the properties Title and one or more Departments for which Manager is responsible.

After you've designed that, you should take a step back and think about how the architecture is to be used. Promotion is one idea that you might want to implement in your design. How would you take a person object and make it an employee object, and how would you take an employee object and make it a manager object? What about the reverse? Also, it might not be necessary to expose a manager object as anything more than a person object to a particular client.

A real-life example might be a car. A car has your typical interface such as a pedal for accelerating, another pedal for braking, a wheel for turning left or right, and so forth. However, another interface is revealed when you think of a mechanic working on your car. He has a completely different interface to the car, such as tuning the engine or changing the oil. In that case, to expect the car's driver to know the car's mechanic interface would be inappropriate. Similarly, the mechanic wouldn't need to know the driver interface, although, I'd like him to know how to drive. That also means that any other car with the same driver interface is easily interchangeable, and the driver of the car doesn't have to change or learn anything new.

Of course in Java, the concept of an interface is used quite often. One might ask how dynamic proxies tie into that use of interfaces. Put simply, dynamic proxies allow you to treat any object as any interface. Sometimes mapping is involved, or the underlying object might not quite match the interface, but overall, that concept can be quite powerful.

Similar to the car example above, you could have a Bus interface that has a different, but similar BusDriver interface. Most people, who know how to drive a car, know mostly what is needed to drive a bus. Or you could have a similar BoatDriver interface but instead of pedals, you have the concept of a throttle and, instead of braking, you have reverse throttle.

In the case of a BusDriver interface, you could probably use a direct map of the Driver interface onto the underlying Bus object and still be able to drive the bus. The BoatDriver interface would most likely call for a mapping of the pedal and brake methods to the throttle method of the underlying Boat object.

By using an abstract data type to represent the underlying object, you can simply put a Person interface onto the data type, fill in person fields, and subsequently come in after that person is hired and use the Employee interface on the same underlying object. The class diagram now looks like Figure 3:

public interface IPerson { public String getName(); public String getAddress(); public String getPhoneNumber(); public void setName(String name); public void setAddress(String address); public void setPhoneNumber(String phoneNumber); } public interface IEmployee extends IPerson { public String getSSN(); public String getDepartment(); public Float getSalary(); public void setSSN(String ssn); public void setDepartment(String department); public void setSalary(String salary); } public interface IManager extends IEmployee { public String getTitle(); public String[] getDepartments(); public void setTitle(String title); public void setDepartments(String[] departments); } public class ViewProxy implements InvocationHandler { private Map map; public static Object newInstance(Map map, Class[] interfaces) { return Proxy.newProxyInstance(map.getClass().getClassLoader(), interfaces, new ViewProxy(map)); } public ViewProxy(Map map) { this.map = map; } public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { Object result; String methodName = m.getName(); if (methodName.startsWith("get")) { String name = methodName.substring(methodName.indexOf("get")+3); return map.get(name); } else if (methodName.startsWith("set")) { String name = methodName.substring(methodName.indexOf("set")+3); map.put(name, args[0]); return null; } else if (methodName.startsWith("is")) { String name = methodName.substring(methodName.indexOf("is")+2); return(map.get(name)); } return null; } }