CodeGym /Java-Blog /Random-DE /Dynamische Proxys in Java
Autor
Jesse Haniel
Lead Software Architect at Tribunal de Justiça da Paraíba

Dynamische Proxys in Java

Veröffentlicht in der Gruppe Random-DE
Hallo! Heute werden wir uns mit einem ziemlich wichtigen und interessanten Thema befassen: der Erstellung dynamischer Proxy-Klassen in Java. Es ist nicht ganz einfach, also versuchen wir es anhand von Beispielen herauszufinden :) Die wichtigste Frage ist also: Was sind dynamische Proxys und wozu dienen sie? Eine Proxy-Klasse ist eine Art „Add-on“ zur Originalklasse, das es uns ermöglicht, das Verhalten der Originalklasse bei Bedarf zu ändern. Was bedeutet „Verhalten ändern“ und wie funktioniert das? Betrachten Sie ein einfaches Beispiel. Angenommen, wir haben eine Person- Schnittstelle und eine einfache Man -Klasse, die diese Schnittstelle implementiert

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.
}
Unsere Man- Klasse verfügt über drei Methoden: „introducing“, „sayAge“ und „sayWhereFrom“. Stellen Sie sich vor, wir hätten diese Klasse als Teil einer handelsüblichen JAR-Bibliothek erhalten und könnten ihren Code nicht einfach umschreiben. Aber wir müssen auch sein Verhalten ändern. Wir wissen beispielsweise nicht, welche Methode für unser Objekt aufgerufen werden könnte, möchten aber, dass unsere Person „Hallo!“ sagt. (Niemand mag jemanden, der unhöflich ist), wenn eine der Methoden aufgerufen wird. Dynamische Proxys – 2Was sollen wir in dieser Situation tun? Wir brauchen ein paar Dinge:
  1. InvocationHandler

Was ist das? InvocationHandler ist eine spezielle Schnittstelle, mit der wir jeden Methodenaufruf unseres Objekts abfangen und das zusätzliche Verhalten hinzufügen können, das wir benötigen. Wir müssen unseren eigenen Interceptor erstellen, dh eine Klasse erstellen, die diese Schnittstelle implementiert. Das ist ziemlich einfach:

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;
   }
}
Wir müssen nur eine Schnittstellenmethode implementieren: invoke() . Und nebenbei macht es das, was wir brauchen: Es fängt alle Methodenaufrufe an unserem Objekt ab und fügt das nötige Verhalten hinzu (innerhalb der invoke()- Methode geben wir „Hi!“ an die Konsole aus).
  1. Das ursprüngliche Objekt und seine Proxys.
Wir erstellen unser ursprüngliches Man- Objekt und ein „Add-on“ (Proxy) dafür:

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());

   }
}
Das sieht nicht ganz einfach aus! Ich habe speziell für jede Codezeile einen Kommentar hinzugefügt. Schauen wir uns genauer an, was los ist. In der ersten Zeile erstellen wir einfach das Originalobjekt, für das wir Proxys erstellen. Die folgenden zwei Zeilen könnten Ihnen Schwierigkeiten bereiten:

 // 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();
Eigentlich passiert hier nichts wirklich Besonderes :) In der vierten Zeile verwenden wir die spezielle Proxy- Klasse und ihre statische newProxyInstance()- Methode:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Diese Methode erstellt lediglich unser Proxy-Objekt. Wir übergeben der Methode die Informationen über die ursprüngliche Klasse, die wir im letzten Schritt erhalten haben (ihren ClassLoader und eine Liste ihrer Schnittstellen), sowie das zuvor erstellte InvocationHandler -Objekt. Die Hauptsache ist, nicht zu vergessen, unser ursprüngliches Arnold- Objekt an den Aufruf-Handler zu übergeben, sonst gibt es nichts zu „verarbeiten“ :) Was haben wir am Ende herausgefunden? Wir haben jetzt ein Proxy-Objekt: ProxyArnold . Es kann beliebige Methoden der Person- Schnittstelle aufrufen. Warum? Weil wir hier eine Liste aller Schnittstellen gegeben haben:

// 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));
Jetzt kennt es alle Methoden der Person- Schnittstelle. Darüber hinaus haben wir an unseren Proxy ein PersonInvocationHandler- Objekt übergeben, das für die Arbeit mit dem Arnold- Objekt konfiguriert ist:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Wenn wir nun eine beliebige Methode der Person- Schnittstelle für das Proxy-Objekt aufrufen, fängt unser Handler den Aufruf ab und führt stattdessen seine eigene invoke()- Methode aus. Versuchen wir, die Methode main() auszuführen ! Konsolenausgabe:

Hi!
Exzellent! Wir sehen, dass anstelle der ursprünglichen Person.introduce() -Methode die invoke()- Methode unseres PersonInvocationHandler() aufgerufen wird:

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

   System.out.println("Hi!");
   return null;
}
"Hallo!" wird auf der Konsole angezeigt, aber das ist nicht genau das Verhalten, das wir wollten :/ Was wir erreichen wollten, ist, zuerst „Hallo!“ anzuzeigen. und rufen Sie dann die ursprüngliche Methode selbst auf. Mit anderen Worten, den Methodenaufruf

proxyArnold.introduce(arnold.getName());
sollte „Hallo! Mein Name ist Arnold“ anzeigen, nicht nur „Hallo!“ Wie können wir das erreichen? Es ist nicht kompliziert: Wir müssen uns nur ein paar Freiheiten mit unserem Handler und der invoke()- Methode nehmen :) Achten Sie darauf, welche Argumente an diese Methode übergeben werden:

public Object invoke(Object proxy, Method method, Object[] args)
Die invoke()- Methode hat Zugriff auf die ursprünglich aufgerufene Methode und auf alle ihre Argumente (Methodenmethode, Object[]-Argumente). Mit anderen Worten: Wenn wir die Methode „proxyArnold.introduce(arnold.getName())“ aufrufen , sodass die Methode „invoke()“ anstelle der Methode „introduce()“ aufgerufen wird , dann haben wir innerhalb dieser Methode Zugriff auf die ursprüngliche Methode „ introduce()“. und sein Argument! Als Ergebnis können wir Folgendes tun:

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);
   }
}
Jetzt haben wir in der invoke()- Methode einen Aufruf zur ursprünglichen Methode hinzugefügt. Wenn wir nun versuchen, den Code aus unserem vorherigen Beispiel auszuführen:

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());
   }
}
dann werden wir sehen, dass jetzt alles so funktioniert, wie es sollte :) Konsolenausgabe:

Hi! My name is Arnold
Wann könnten Sie das brauchen? Eigentlich ziemlich oft. Das Designmuster „Dynamic Proxy“ wird in beliebten Technologien aktiv verwendet ... Ach, übrigens, ich habe vergessen zu erwähnen, dass Dynamic Proxy ein Designmuster ist! Herzlichen Glückwunsch, Sie haben noch etwas gelernt! :) Dynamische Proxys – 3Beispielsweise wird es aktiv in beliebten Technologien und Frameworks im Zusammenhang mit Sicherheit eingesetzt. Stellen Sie sich vor, Sie haben 20 Methoden, die nur von Benutzern ausgeführt werden sollten, die bei Ihrem Programm angemeldet sind. Mithilfe der Techniken, die Sie erlernt haben, könnten Sie diesen 20 Methoden problemlos eine Prüfung hinzufügen, um zu sehen, ob der Benutzer gültige Anmeldeinformationen eingegeben hat, ohne den Bestätigungscode in jeder Methode zu duplizieren. Oder nehmen Sie an, Sie möchten ein Protokoll erstellen, in dem alle Benutzeraktionen aufgezeichnet werden. Dies ist auch einfach über einen Proxy möglich. Selbst jetzt könnten Sie einfach Code zu unserem obigen Beispiel hinzufügen, sodass der Methodenname angezeigt wird, wenn Sie invoke() aufrufen , und das würde ein supereinfaches Protokoll unseres Programms erzeugen :) Abschließend sollten Sie auf eine wichtige Einschränkung achten. Ein Proxy-Objekt arbeitet mit Schnittstellen, nicht mit Klassen. Für eine Schnittstelle wird ein Proxy erstellt. Schauen Sie sich diesen Code an:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Hier erstellen wir einen Proxy speziell für die Person- Schnittstelle. Wenn wir versuchen, einen Proxy für die Klasse zu erstellen, also den Referenztyp zu ändern und versuchen, ihn in die Man- Klasse umzuwandeln, wird das nicht funktionieren.

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

proxyArnold.introduce(arnold.getName());
Ausnahme im Thread „main“ java.lang.ClassCastException: com.sun.proxy.$Proxy0 kann nicht in Man umgewandelt werden. Eine Schnittstelle ist eine absolute Voraussetzung. Proxys arbeiten mit Schnittstellen. Das ist alles für heute :) Nun wäre es gut, ein paar Aufgaben zu lösen! :) Bis zum nächsten Mal!
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION