Entdecken Sie die Dynamic Proxy API

Mit der Einführung der Dynamic Proxy API in Java 1.3 wurde eine große und oft übersehene Verbesserung der Java-Plattform vorgenommen. Die Verwendung für dynamische Proxys ist manchmal schwer zu verstehen. In diesem Artikel möchte ich Ihnen zunächst das Proxy-Entwurfsmuster und dann die java.lang.reflect.ProxyKlasse und die java.lang.reflect.InvocationHandlerBenutzeroberfläche vorstellen , die das Herzstück der Funktionalität von Dynamic Proxy bilden.

Die hier beschriebene Funktionalität kombiniert die dynamischen Java 1.3-Proxys mit abstrakten Datentypen, um diesen Typen eine starke Typisierung zu verleihen. Ich werde auch die Leistungsfähigkeit des dynamischen Proxys diskutieren, indem ich das Konzept der Ansichten in Ihre Java-Programmierung einführe . Zuletzt werde ich eine leistungsstarke Methode vorstellen, mit der Sie Ihren Java-Objekten die Zugriffssteuerung hinzufügen können, natürlich unter Verwendung des dynamischen Proxys.

Definition eines Proxys

Ein Proxy erzwingt, dass Objektmethodenaufrufe indirekt über das Proxy-Objekt erfolgen, das als Ersatz oder Delegat für das zugrunde liegende Objekt fungiert, für das ein Proxy erstellt wird. Proxy-Objekte werden normalerweise so deklariert, dass die Client-Objekte keinen Hinweis darauf haben, dass sie eine Proxy-Objektinstanz haben.

Einige gängige Proxys sind der Zugriffsproxy, Fassaden, Remote-Proxys und virtuelle Proxys. Ein Zugriffsproxy wird verwendet, um eine Sicherheitsrichtlinie für den Zugriff auf einen Dienst oder ein datenbereitstellendes Objekt durchzusetzen. Eine Fassade ist eine einzelne Schnittstelle zu mehreren zugrunde liegenden Objekten. Der Remote-Proxy wird verwendet, um das Client-Objekt vor der Tatsache zu maskieren oder abzuschirmen, dass das zugrunde liegende Objekt remote ist. Ein virtueller Proxy wird verwendet, um eine verzögerte oder Just-in-Time-Instanziierung des realen Objekts durchzuführen.

Der Proxy ist ein grundlegendes Entwurfsmuster, das bei der Programmierung häufig verwendet wird. Einer seiner Nachteile ist jedoch die Spezifität oder enge Kopplung des Proxys mit seinem zugrunde liegenden Objekt. Wenn Sie sich die UML für das Proxy-Entwurfsmuster in Abbildung 1 ansehen, sehen Sie, dass der Proxy, um nützlich und transparent zu sein, normalerweise entweder eine Schnittstelle implementieren oder von einer bekannten Oberklasse erben muss (mit Ausnahme einer Fassade möglicherweise).

Dynamische Proxys

In Java 1.3 hat Sun die Dynamic Proxy API eingeführt. Damit der dynamische Proxy funktioniert, müssen Sie zuerst über eine Proxy-Schnittstelle verfügen. Die Proxy-Schnittstelle ist die Schnittstelle, die von der Proxy-Klasse implementiert wird. Zweitens benötigen Sie eine Instanz der Proxy-Klasse.

Interessanterweise können Sie eine Proxy-Klasse haben, die mehrere Schnittstellen implementiert. Es gibt jedoch einige Einschränkungen für die von Ihnen implementierten Schnittstellen. Es ist wichtig, diese Einschränkungen beim Erstellen Ihres dynamischen Proxys zu berücksichtigen:

  1. Die Proxy-Schnittstelle muss eine Schnittstelle sein. Mit anderen Worten, es kann keine Klasse (oder eine abstrakte Klasse) oder ein Primitiv sein.
  2. Das an den Proxy-Konstruktor übergebene Array von Schnittstellen darf keine Duplikate derselben Schnittstelle enthalten. Sun gibt dies an, und es ist sinnvoll, dass Sie nicht zweimal gleichzeitig versuchen, dieselbe Schnittstelle zu implementieren. Zum Beispiel { IPerson.class, IPerson.class }wäre ein Array illegal, der Code { IPerson.class, IEmployee.class }jedoch nicht. Der Code, der den Konstruktor aufruft, sollte nach diesem Fall suchen und Duplikate herausfiltern.
  3. Alle Schnittstellen müssen ClassLoaderwährend des Bauaufrufs für die angegebenen sichtbar sein . Auch das macht Sinn. Der ClassLoadermuss in der Lage sein, die Schnittstellen für den Proxy zu laden.
  4. Alle nicht öffentlichen Schnittstellen müssen aus demselben Paket stammen. Sie können keine private Schnittstelle aus dem Paket com.xyzund der Proxy-Klasse im Paket haben com.abc. Wenn Sie darüber nachdenken, ist es genauso, wenn Sie eine reguläre Java-Klasse programmieren. Sie konnten auch keine nicht öffentliche Schnittstelle aus einem anderen Paket mit einer regulären Klasse implementieren.
  5. Die Proxy-Schnittstellen dürfen keinen Methodenkonflikt aufweisen. Sie können nicht zwei Methoden verwenden, die dieselben Parameter verwenden, aber unterschiedliche Typen zurückgeben. Beispielsweise können die Methoden public void foo()und public String foo()nicht in derselben Klasse definiert werden, da sie dieselbe Signatur haben, aber unterschiedliche Typen zurückgeben (siehe Die Java-Sprachspezifikation ). Auch dies gilt für eine reguläre Klasse.
  6. Die resultierende Proxy-Klasse darf die Grenzen der VM nicht überschreiten, z. B. die Begrenzung der Anzahl der Schnittstellen, die implementiert werden können.

Um eine tatsächliche dynamische Proxy-Klasse zu erstellen, müssen Sie lediglich die java.lang.reflect.InvocationHandlerSchnittstelle implementieren :

öffentliche Klasse MyDynamicProxyClass implementiert java.lang.reflect.InvocationHandler {Object obj; public MyDynamicProxyClass (Object obj) {this.obj = obj; } public Object invoke (Objekt-Proxy, Methode m, Object [] args) löst Throwable aus {try {// do some} catch (InvocationTargetException e) {throw e.getTargetException (); } catch (Ausnahme e) {throw e; } // etwas zurückbringen } }

Das ist alles dazu! Ja wirklich! Ich lüge nicht! Okay, Sie müssen auch Ihre eigentliche Proxy-Schnittstelle haben:

öffentliche Schnittstelle MyProxyInterface {public Object MyMethod (); }}

Um diesen dynamischen Proxy tatsächlich zu verwenden, sieht der Code folgendermaßen aus:

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

Da ich weiß, dass der obige Code einfach schrecklich hässlich ist, möchte ich ihn in einer Art Factory-Methode verstecken. Anstatt diesen unordentlichen Code im Client-Code zu haben, füge ich diese Methode zu meinem hinzu MyDynamicProxyClass:

statisches öffentliches Objekt newInstance (Object obj, Class [] -Schnittstellen) {return java.lang.reflect.Proxy.newProxyInstance (obj.getClass (). getClassLoader (), Schnittstellen, neue MyDynamicProxyClass (obj)); }}

Dadurch kann ich stattdessen den folgenden Clientcode verwenden:

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

Das ist viel sauberer Code. In Zukunft könnte es eine gute Idee sein, eine Factory-Klasse zu haben, die den gesamten Code vollständig vor dem Client verbirgt, sodass der Client-Code eher wie folgt aussieht:

MyProxyInterface foo = Builder.newProxyInterface (); 

Insgesamt ist die Implementierung eines dynamischen Proxys recht einfach. Hinter dieser Einfachheit steckt jedoch große Kraft. Diese große Leistung ergibt sich aus der Tatsache, dass Ihr dynamischer Proxy jede Schnittstelle oder Schnittstellengruppe implementieren kann. Ich werde dieses Konzept im nächsten Abschnitt untersuchen.

Abstrakte Daten

Das beste Beispiel für abstrakte Daten sind die Java-Auflistungsklassen wie z

java.util.ArrayList

,

java.util.HashMap

, oder

java.util.Vector

. Diese Auflistungsklassen können jedes Java-Objekt enthalten. Sie sind in ihrer Verwendung in Java von unschätzbarem Wert. Das Konzept der abstrakten Datentypen ist leistungsstark, und diese Klassen bringen die Leistung von Sammlungen in jeden Datentyp.

Die beiden zusammenbinden

By combining the concept of dynamic proxies with abstract data types, you can gain all the benefits of abstract data types with strong typing. In addition, you can easily use the proxy class to implement access control, virtual proxies, or any other useful proxy type. By masking the actual creation and use of proxies from the client code, you can make changes to the underlying proxy code without affecting the client code.

The concept of a view

When architecting a Java program, it is common to run into design problems in which a class must display multiple, different interfaces to client code. Take Figure 2 for example:

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; } }