JavaRush /Java-Blog /Random-DE /Dynamische Proxys in Java

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 lautet also: Was sind dynamische Proxys und wozu dienen sie? Eine Proxy-Klasse ist eine Art „Überbau“ über der ursprünglichen Klasse, der es uns ermöglicht, ihr Verhalten bei Bedarf zu ändern. Was bedeutet es, Verhalten zu ändern und wie funktioniert es? Schauen wir uns ein einfaches Beispiel an. Nehmen wir an, wir haben eine Schnittstelle Personund eine einfache Klasse Man, die diese Schnittstelle implementiert
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 + " Jahre");
   }

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

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

   //..геттеры, сеттеры, и т.д.
}
In unserem Kurs Mangibt es drei Methoden: Stellen Sie sich vor, geben Sie Ihr Alter an und geben Sie an, woher Sie kommen. Stellen wir uns vor, dass wir diese Klasse als Teil einer vorgefertigten JAR-Bibliothek erhalten haben und ihren Code nicht einfach übernehmen und neu schreiben können. Allerdings müssen wir sein Verhalten ändern. Wir wissen beispielsweise nicht, welche Methode für unser Objekt aufgerufen wird, und möchten daher, dass die Person zuerst „Hallo!“ sagt, wenn sie eines von ihnen aufruft. (Niemand mag jemanden, der unhöflich ist). Dynamische Proxys – 1Wie sollen wir in einer solchen Situation handeln? Wir werden ein paar Dinge brauchen:
  1. InvocationHandler

Was ist das? Es kann wörtlich als „Anrufabfangjäger“ übersetzt werden. Dies beschreibt seinen Zweck ziemlich genau. InvocationHandlerist eine spezielle Schnittstelle, die es uns ermöglicht, alle Methodenaufrufe unseres Objekts abzufangen und das zusätzliche Verhalten hinzuzufügen, das wir benötigen. Wir müssen unseren eigenen Interceptor erstellen, das heißt, eine Klasse erstellen und diese Schnittstelle implementieren. Es ist ganz 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("Hallo!");
       return null;
   }
}
Wir müssen nur eine Schnittstellenmethode implementieren – invoke(). Es tut tatsächlich, was wir brauchen – es fängt alle Methodenaufrufe an unser Objekt ab und fügt das erforderliche Verhalten hinzu (hier invoke()geben wir „Hallo!“ an die Konsole innerhalb der Methode aus).
  1. Das Originalobjekt und sein Proxy.
Erstellen wir ein Originalobjekt Manund einen „Überbau“ (Proxy) dafür:
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

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

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

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

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

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

   }
}
Sieht nicht ganz einfach aus! Ich habe speziell für jede Codezeile einen Kommentar geschrieben: Schauen wir uns genauer an, was dort passiert.

In der ersten Zeile erstellen wir einfach das Originalobjekt, für das wir einen Proxy erstellen. Die folgenden zwei Zeilen könnten Sie verwirren:
//Получаем загрузчик класса у оригинального ein Objektа
ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

//Получаем все интерфейсы, которые реализует оригинальный ein Objekt
Class[] interfaces = vasia.getClass().getInterfaces();
Aber hier passiert eigentlich nichts Besonderes :) Um einen Proxy zu erstellen, benötigen wir ClassLoaderden (Klassenlader) des Originalobjekts und eine Liste aller Schnittstellen, die unsere Originalklasse (d. h. Man) implementiert. Wenn Sie nicht wissen, was es ist ClassLoader, können Sie diesen Artikel über das Laden von Klassen in die JVM oder diesen auf Habré lesen , aber beschäftigen Sie sich noch nicht zu sehr damit. Denken Sie daran, dass wir einige zusätzliche Informationen erhalten, die wir dann zum Erstellen des Proxy-Objekts benötigen. In der vierten Zeile verwenden wir eine spezielle Klasse Proxyund ihre statische Methode newProxyInstance():
//Создаем прокси нашего ein Objektа vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Diese Methode erstellt lediglich unser Proxy-Objekt. An die Methode übergeben wir die Informationen über die ursprüngliche Klasse, die wir im vorherigen Schritt erhalten haben (sie ClassLoaderund die Liste ihrer Schnittstellen), sowie das Objekt des Interceptors, das wir zuvor erstellt haben – InvocationHandler„a“. Die Hauptsache ist: Vergessen Sie nicht, unser ursprüngliches Objekt an den Abfangjäger zu übergeben vasia, sonst hat er nichts zum „Abfangen“ :) Was haben wir am Ende herausgefunden? Wir haben jetzt ein Proxy-Objekt vasiaProxy. Es kann beliebige SchnittstellenmethodenPerson aufrufen . Warum? Weil wir ihm eine Liste aller Schnittstellen übergeben haben – hier:
//Получаем все интерфейсы, которые реализует оригинальный ein Objekt
Class[] interfaces = vasia.getClass().getInterfaces();

//Создаем прокси нашего ein Objektа vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Jetzt kennt er alle Schnittstellenmethoden Person. Darüber hinaus haben wir unserem Proxy ein Objekt übergeben, das PersonInvocationHandlerfür die Arbeit mit dem Objekt konfiguriert ist vasia:
//Создаем прокси нашего ein Objektа vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Wenn wir nun eine beliebige Schnittstellenmethode für das Proxy-Objekt aufrufen Person, „fängt“ unser Interceptor diesen Aufruf ab und führt stattdessen seine eigene Methode aus invoke(). Versuchen wir, die Methode auszuführen main()! Konsolenausgabe: Hallo! Großartig! Wir sehen, dass unsere Methode anstelle der echten Methode Person.introduce()heißt : invoke()PersonInvocationHandler()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   System.out.println("Hallo!");
   return null;
}
Und auf der Konsole wurde „Hallo!“ angezeigt. Aber das ist nicht genau das Verhalten, das wir erreichen wollten :/ Nach unserer Idee sollte zuerst „Hallo!“ angezeigt werden, und dann sollte die Methode selbst, die wir aufrufen, funktionieren. Mit anderen Worten, dieser Methodenaufruf:
proxyVasia.introduce(vasia.getName());
sollte auf der Konsole „Hallo!“ ausgeben. Mein Name ist Vasya“ und nicht nur „Hallo!“ Wie können wir das erreichen? Nichts Kompliziertes: Sie müssen nur ein wenig an unserem Interceptor und unserer Methode herumbasteln invoke():) Achten Sie darauf, welche Argumente an diese Methode übergeben werden:
public Object invoke(Object proxy, Method method, Object[] args)
Eine Methode invoke()hat Zugriff auf die Methode, die sie stattdessen aufruft, und auf alle ihre Argumente (Methodenmethode, Object[]-Argumente). Mit anderen Worten: Wenn wir eine Methode aufrufen proxyVasia.introduce(vasia.getName())und statt einer Methode introduce()eine Methode aufgerufen wird invoke(), haben wir innerhalb dieser Methode Zugriff sowohl auf die ursprüngliche Methode introduce()als auch auf ihr Argument! Als Ergebnis können wir so etwas 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("Hallo!");
       return method.invoke(person, args);
   }
}
invoke()Jetzt haben wir der Methode einen Aufruf der 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) {

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

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

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

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

       //Вызываем у прокси ein Objektа один из методов нашего оригинального ein Objektа
       proxyVasia.introduce(vasia.getName());
   }
}
dann werden wir sehen, dass jetzt alles so funktioniert, wie es sollte :) Konsolenausgabe: Hallo! Mein Name ist Vasya. Wo könnten Sie es brauchen? Tatsächlich an vielen Orten. Das Designmuster „Dynamischer Proxy“ wird in beliebten Technologien aktiv verwendet ... und ich habe übrigens vergessen, Ihnen zu sagen, dass Dynamic Proxyes sich um ein Muster handelt ! Herzlichen Glückwunsch, Sie haben wieder etwas gelernt! :) Dynamische Proxys – 2Daher wird es aktiv in beliebten Technologien und Frameworks im Zusammenhang mit Sicherheit eingesetzt. Stellen Sie sich vor, Sie haben 20 Methoden, die nur von angemeldeten Benutzern Ihres Programms ausgeführt werden können. Mit den von Ihnen erlernten Techniken können Sie diesen 20 Methoden ganz einfach eine Überprüfung hinzufügen, um zu sehen, ob der Benutzer einen Benutzernamen und ein Passwort eingegeben hat, ohne den Bestätigungscode in jeder Methode separat zu duplizieren. Wenn Sie beispielsweise ein Protokoll erstellen möchten, in dem alle Benutzeraktionen aufgezeichnet werden, ist dies auch einfach über einen Proxy möglich. Sie können sogar jetzt: Fügen Sie dem Beispiel einfach Code hinzu, damit der Name der Methode beim Aufruf in der Konsole angezeigt wird invoke(), und Sie erhalten ein einfaches Protokoll unseres Programms :) Achten Sie am Ende der Vorlesung auf eines Wichtige Einschränkung . Das Erstellen eines Proxy-Objekts erfolgt auf Schnittstellenebene, nicht auf Klassenebene. Für die Schnittstelle wird ein Proxy erstellt. Schauen Sie sich diesen Code an:
//Создаем прокси нашего ein Objektа vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Hier erstellen wir einen Proxy speziell für die Schnittstelle Person. Wenn wir versuchen, einen Proxy für die Klasse zu erstellen, das heißt, wir ändern den Typ des Links und versuchen, ihn in die Klasse umzuwandeln Man, wird nichts funktionieren.
Man proxyVasia = (Man) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

proxyVasia.introduce(vasia.getName());
Ausnahme im Thread „main“ java.lang.ClassCastException: com.sun.proxy.$Proxy0 kann nicht in Man umgewandelt werden. Das Vorhandensein einer Schnittstelle ist eine zwingende Voraussetzung. Proxy funktioniert auf Schnittstellenebene. Das ist alles für heute :) Als zusätzliches Material zum Thema Proxys kann ich Ihnen ein hervorragendes Video und auch einen guten Artikel empfehlen . Nun wäre es schön, ein paar Probleme zu lösen! :) Auf Wiedersehen!
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION