CodeGym /Java Blog /Willekeurig /Dynamische proxy's in Java
John Squirrels
Niveau 41
San Francisco

Dynamische proxy's in Java

Gepubliceerd in de groep Willekeurig
Hoi! Vandaag zullen we een vrij belangrijk en interessant onderwerp bespreken: het creëren van dynamische proxyklassen in Java. Het is niet erg eenvoudig, dus we zullen proberen het uit te zoeken aan de hand van voorbeelden :) Dus de belangrijkste vraag: wat zijn dynamische proxy's en waar zijn ze voor? Een proxyklasse is een soort "add-on" bovenop de oorspronkelijke klasse, waarmee we het gedrag van de oorspronkelijke klasse indien nodig kunnen wijzigen. Wat betekent het om "gedrag te veranderen" en hoe werkt dat? Overweeg een eenvoudig voorbeeld. Stel dat we een Person- interface hebben en een eenvoudige Man- klasse die deze interface implementeert

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.
}
Onze Man- klasse heeft 3 methoden: introduce, sayAge en sayWhereFrom. Stel je voor dat we deze klasse hebben gekregen als onderdeel van een kant-en-klare JAR-bibliotheek en dat we de code niet zomaar kunnen herschrijven. Maar we moeten ook zijn gedrag veranderen. We weten bijvoorbeeld niet welke methode op ons object kan worden aangeroepen, maar we willen dat onze persoon "Hallo!" (niemand houdt van iemand die onbeleefd is) wanneer een van de methoden wordt aangeroepen. Dynamische proxy's - 2Wat moeten we doen in deze situatie? We hebben een paar dingen nodig:
  1. InvocationHandler

Wat is dit? InvocationHandler is een speciale interface waarmee we elke methodeaanroep naar ons object kunnen onderscheppen en het aanvullende gedrag kunnen toevoegen dat we nodig hebben. We moeten onze eigen interceptor maken, dwz een klasse maken die deze interface implementeert. Dit is vrij eenvoudig:

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;
   }
}
We hoeven slechts één interfacemethode te implementeren: invoke() . En trouwens, het doet wat we nodig hebben: het onderschept alle methodeaanroepen naar ons object en voegt het nodige gedrag toe (binnen de methode invoke() voeren we "Hallo!" uit naar de console).
  1. Het oorspronkelijke object en zijn proxy's.
We maken ons originele Man- object en een "add-on" (proxy) ervoor:

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

   }
}
Dit ziet er niet zo eenvoudig uit! Ik heb specifiek een opmerking toegevoegd voor elke regel code. Laten we eens nader bekijken wat er aan de hand is. In de eerste regel maken we gewoon het originele object waarvoor we proxy's gaan maken. De volgende twee regels kunnen problemen veroorzaken:

 // 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();
Eigenlijk gebeurt hier niets bijzonders :) In de vierde regel gebruiken we de speciale Proxy- klasse en de statische newProxyInstance()- methode:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Deze methode maakt gewoon ons proxy-object. We geven aan de methode de informatie door over de originele klasse, die we in de laatste stap hebben ontvangen (zijn ClassLoader en een lijst met zijn interfaces), evenals het eerder gemaakte InvocationHandler- object. Het belangrijkste is om niet te vergeten ons originele arnold- object door te geven aan de aanroephandler, anders is er niets om te "afhandelen" :) Waar zijn we mee geëindigd? We hebben nu een proxy-object: proxyArnold . Het kan elke methode van de Person- interface aanroepen . Waarom? Omdat we het hier een lijst met alle interfaces hebben gegeven:

// 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));
Nu kent het alle methoden van de persoonsinterface . Daarnaast hebben we aan onze proxy een PersonInvocationHandler- object doorgegeven dat is geconfigureerd om met het arnold- object te werken:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Als we nu een methode van de Person- interface op het proxy-object aanroepen, onderschept onze handler de oproep en voert in plaats daarvan zijn eigen invoke()- methode uit. Laten we proberen de methode main() uit te voeren ! Console-uitvoer:

Hi!
Uitstekend! We zien dat in plaats van de oorspronkelijke methode Person.introduce() de methode invoke() van onze PersonInvocationHandler() wordt aangeroepen:

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

   System.out.println("Hi!");
   return null;
}
"Hoi!" wordt weergegeven op de console, maar dit is niet precies het gedrag dat we wilden :/ Wat we probeerden te bereiken, was om eerst "Hallo!" en dan de oorspronkelijke methode zelf aanroepen, met andere woorden de methodeaanroep

proxyArnold.introduce(arnold.getName());
zou "Hallo! Mijn naam is Arnold" moeten weergeven, niet alleen "Hallo!" Hoe kunnen we dit bereiken? Het is niet ingewikkeld: we moeten gewoon wat vrijheden nemen met onze handler en de methode invoke() :) Let op welke argumenten aan deze methode worden doorgegeven:

public Object invoke(Object proxy, Method method, Object[] args)
De methode invoke() heeft toegang tot de oorspronkelijk aangeroepen methode en tot al zijn argumenten (methode Method, Object[] args). Met andere woorden, als we de proxyArnold.introduce(arnold.getName()) methode aanroepen zodat de invoke() methode wordt aangeroepen in plaats van de introduce() methode, dan hebben we binnen deze methode toegang tot de originele introduce() methode en zijn betoog! Hierdoor kunnen we dit doen:

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);
   }
}
Nu hebben we in de methode invoke() een aanroep toegevoegd aan de oorspronkelijke methode. Als we nu proberen de code uit ons vorige voorbeeld uit te voeren:

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());
   }
}
dan zullen we zien dat alles nu werkt zoals het hoort :) Console-uitvoer:

Hi! My name is Arnold
Wanneer heb je dit nodig? Eigenlijk best vaak. Het ontwerppatroon "dynamische proxy" wordt actief gebruikt in populaire technologieën... Oh, trouwens, ik vergat te vermelden dat Dynamic Proxy een ontwerppatroon is! Gefeliciteerd, je hebt er weer een geleerd! :) Dynamische proxy's - 3Het wordt bijvoorbeeld actief gebruikt in populaire technologieën en frameworks met betrekking tot beveiliging. Stel je voor dat je 20 methoden hebt die alleen mogen worden uitgevoerd door gebruikers die zijn aangemeld bij je programma. Met behulp van de technieken die u hebt geleerd, kunt u eenvoudig aan deze 20 methoden een controle toevoegen om te zien of de gebruiker geldige inloggegevens heeft ingevoerd zonder de verificatiecode in elke methode te dupliceren. Of stel dat u een logboek wilt maken waarin alle gebruikersacties worden vastgelegd. Dit is ook eenvoudig te doen met behulp van een proxy. Zelfs nu zou je gewoon code aan ons voorbeeld hierboven kunnen toevoegen, zodat de naam van de methode wordt weergegeven wanneer je invoke() aanroept , en dat zou een supereenvoudig logboek van ons programma opleveren :) Tot slot, let op een belangrijke beperking. Een proxy-object werkt met interfaces, niet met klassen. Er wordt een proxy gemaakt voor een interface. Bekijk deze code eens:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Hier maken we een proxy specifiek voor de Person- interface. Als we proberen een proxy voor de klasse te maken, dwz het type referentie wijzigen en proberen naar de klasse Man te casten , zal het niet werken.

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

proxyArnold.introduce(arnold.getName());
Uitzondering in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 kan niet naar Man worden gecast Het hebben van een interface is een absolute vereiste. Proxy's werken met interfaces. Dat was alles voor vandaag :) Nou, nu zou het goed zijn om een ​​paar taken op te lossen! :) Tot de volgende keer!
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION