CodeGym /Java blog /Tilfældig /Dynamiske proxyer i Java
John Squirrels
Niveau
San Francisco

Dynamiske proxyer i Java

Udgivet i gruppen
Hej! I dag vil vi overveje et ret vigtigt og interessant emne: oprettelsen af ​​dynamiske proxy-klasser i Java. Det er ikke særlig simpelt, så vi vil prøve at finde ud af det ved hjælp af eksempler :) Så det vigtigste spørgsmål: hvad er dynamiske proxyer, og hvad er de til? En proxy-klasse er en slags "add-on" oven på den originale klasse, som giver os mulighed for at ændre den originale klasses adfærd, hvis det er nødvendigt. Hvad vil det sige at "ændre adfærd", og hvordan virker det? Overvej et simpelt eksempel. Antag, at vi har en Person- grænseflade og en simpel Man- klasse, der implementerer denne grænseflade

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.
}
Vores Man- klasse har 3 metoder: introducere, sayAge og sayWhereFrom. Forestil dig, at vi fik denne klasse som en del af et JAR-bibliotek, der er helt klar, og vi kan ikke bare omskrive dens kode. Men vi skal også ændre dens adfærd. For eksempel ved vi ikke, hvilken metode der kan kaldes på vores objekt, men vi vil have vores person til at sige "Hej!" (ingen kan lide en, der er uhøflig), når nogen af ​​metoderne kaldes. Dynamiske proxyer - 2Hvad skal vi gøre i denne situation? Vi skal bruge et par ting:
  1. InvocationHandler

Hvad er dette? InvocationHandler er en speciel grænseflade, der lader os opsnappe ethvert metodekald til vores objekt og tilføje den yderligere adfærd, vi har brug for. Vi skal lave vores egen interceptor, dvs. skabe en klasse, der implementerer denne grænseflade. Dette er ret simpelt:

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;
   }
}
Vi skal kun implementere én grænseflademetode: invoke() . Og i øvrigt gør den, hvad vi har brug for: den opsnapper alle metodekald til vores objekt og tilføjer den nødvendige adfærd (inde i invoke()- metoden udsender vi "Hej!" til konsollen).
  1. Det oprindelige objekt og dets fuldmagter.
Vi skaber vores originale Man -objekt og en "add-on" (proxy) til det:

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

   }
}
Det her ser ikke særlig simpelt ud! Jeg tilføjede specifikt en kommentar for hver linje kode. Lad os se nærmere på, hvad der foregår. I den første linje laver vi simpelthen det originale objekt, som vi vil oprette proxyer til. De følgende to linjer kan give dig problemer:

 // 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();
Der sker faktisk ikke noget særligt her :) I den fjerde linje bruger vi den specielle Proxy- klasse og dens statiske newProxyInstance()- metode:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Denne metode opretter blot vores proxy-objekt. Vi videregiver til metoden informationen om den originale klasse, som vi modtog i det sidste trin (dens ClassLoader og en liste over dens grænseflader), såvel som det tidligere oprettede InvocationHandler- objekt. Det vigtigste er ikke at glemme at videregive vores originale arnoldobjekt til invokationsbehandleren, ellers er der ikke noget at "håndtere" :) Hvad endte vi med? Vi har nu et proxy-objekt: proxyArnold . Det kan kalde alle metoder i Person- grænsefladen. Hvorfor? Fordi vi gav den en liste over alle grænseflader her:

// 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 kender den til alle metoderne til Person- grænsefladen. Derudover sendte vi et PersonInvocationHandler -objekt, der var konfigureret til at arbejde med arnold- objektet, til vores proxy:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Hvis vi nu kalder en hvilken som helst metode i Person- grænsefladen på proxy-objektet, opsnapper vores behandler kaldet og udfører sin egen invoke()- metode i stedet. Lad os prøve at køre main() metoden! Konsoludgang:

Hi!
Fremragende! Vi ser, at i stedet for den oprindelige Person.introduce()- metode, kaldes invoke()- metoden i vores PersonInvocationHandler() :

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

   System.out.println("Hi!");
   return null;
}
"Hej!" vises på konsollen, men det er ikke lige den adfærd, vi ønskede :/ Det, vi forsøgte at opnå, er først at vise "Hej!" og kalder så selve den oprindelige metode. Med andre ord metodekaldet

proxyArnold.introduce(arnold.getName());
skal vise "Hej! Jeg hedder Arnold", ikke blot "Hej!" Hvordan kan vi opnå dette? Det er ikke kompliceret: vi skal bare tage nogle friheder med vores handler og invoke () -metoden :) Vær opmærksom på, hvilke argumenter der sendes til denne metode:

public Object invoke(Object proxy, Method method, Object[] args)
Metoden invoke() har adgang til den oprindeligt påkaldte metode og til alle dens argumenter (Method method, Object[] args). Med andre ord, hvis vi kalder proxyArnold.introduce(arnold.getName())- metoden, så invoke()- metoden kaldes i stedet for introduce()- metoden, så har vi inde i denne metode adgang til den originale introduce()- metode og dets argument! Som et resultat kan vi gøre dette:

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 i invoke() -metoden har vi tilføjet et kald til den originale metode. Hvis vi nu prøver at køre koden fra vores tidligere eksempel:

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());
   }
}
så skal vi se at nu virker alt som det skal :) Konsol output:

Hi! My name is Arnold
Hvornår kan du få brug for dette? Faktisk ret ofte. Det "dynamiske proxy" designmønster bruges aktivt i populære teknologier... Åh, forresten, jeg glemte at nævne, at Dynamic Proxy er et designmønster! Tillykke, du har lært en mere! :) Dynamiske proxyer - 3For eksempel bruges det aktivt i populære teknologier og rammer relateret til sikkerhed. Forestil dig, at du har 20 metoder, der kun bør udføres af brugere, der er logget ind på dit program. Ved at bruge de teknikker, du har lært, kan du nemt tilføje til disse 20 metoder en kontrol for at se, om brugeren har indtastet gyldige legitimationsoplysninger uden at duplikere bekræftelseskoden i hver metode. Eller antag, at du vil oprette en log, hvor alle brugerhandlinger vil blive registreret. Dette er også nemt at gøre ved hjælp af en proxy. Selv nu kan du blot tilføje kode til vores eksempel ovenfor, så metodenavnet vises, når du kalder invoke() , og det ville producere en super simpel log over vores program :) Afslutningsvis skal du være opmærksom på en vigtig begrænsning. Et proxyobjekt fungerer med grænseflader, ikke klasser. Der oprettes en proxy til en grænseflade. Tag et kig på denne kode:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Her opretter vi en proxy specifikt til Person -grænsefladen. Hvis vi forsøger at oprette en proxy for klassen, dvs. ændre referencetypen og forsøge at caste til Man- klassen, vil det ikke virke.

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

proxyArnold.introduce(arnold.getName());
Undtagelse i tråden "hoved" java.lang.ClassCastException: com.sun.proxy.$Proxy0 kan ikke castes til mand At have en grænseflade er et absolut krav. Proxyer arbejder med grænseflader. Det var alt for i dag :) Nå, nu ville det være godt at løse et par opgaver! :) Indtil næste gang!
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION