CodeGym /Java-blogg /Tilfeldig /Dynamiske proxyer i Java
John Squirrels
Nivå
San Francisco

Dynamiske proxyer i Java

Publisert i gruppen
Hei! I dag vil vi vurdere et ganske viktig og interessant emne: opprettelsen av dynamiske proxy-klasser i Java. Det er ikke veldig enkelt, så vi skal prøve å finne det ut ved hjelp av eksempler :) Så det viktigste spørsmålet: hva er dynamiske proxyer og hva er de for? En proxy-klasse er en slags "add-on" på toppen av den originale klassen, som lar oss endre den opprinnelige klassens oppførsel om nødvendig. Hva vil det si å «endre atferd» og hvordan fungerer det? Tenk på et enkelt eksempel. Anta at vi har et Person- grensesnitt og en enkel Man- klasse som implementerer dette grensesnittet

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.
}
Man- klassen vår har 3 metoder: introdusere, si Alder og siHvorfra. Tenk deg at vi fikk denne klassen som en del av et hyllevare JAR-bibliotek, og vi kan ikke bare skrive om koden. Men vi må også endre atferden. For eksempel vet vi ikke hvilken metode som kan kalles på objektet vårt, men vi vil at personen vår skal si "Hei!" (ingen liker noen som er uhøflige) når noen av metodene kalles. Dynamiske proxyer - 2Hva bør vi gjøre i denne situasjonen? Vi trenger et par ting:
  1. InvocationHandler

Hva er dette? InvocationHandler er et spesielt grensesnitt som lar oss fange opp ethvert metodekall til objektet vårt og legge til den ekstra atferden vi trenger. Vi må lage vår egen interceptor, dvs. lage en klasse som implementerer dette grensesnittet. Dette er ganske enkelt:

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 trenger bare å implementere én grensesnittmetode: invoke() . Og, forresten, den gjør det vi trenger: den fanger opp alle metodekall til objektet vårt og legger til den nødvendige oppførselen (inne i invoke()- metoden sender vi "Hi!" til konsollen).
  1. Det opprinnelige objektet og dets proxyer.
Vi lager vårt originale Man- objekt og et "tillegg" (proxy) for 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());

   }
}
Dette ser ikke veldig enkelt ut! Jeg la spesielt til en kommentar for hver linje med kode. La oss se nærmere på hva som skjer. I den første linjen lager vi ganske enkelt det originale objektet som vi skal lage proxyer for. De følgende to linjene kan forårsake 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();
Det er faktisk ikke noe spesielt som skjer her :) På den fjerde linjen bruker vi den spesielle Proxy- klassen og dens statiske newProxyInstance()- metoden:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Denne metoden oppretter bare proxy-objektet vårt. Vi overfører til metoden informasjonen om den opprinnelige klassen, som vi mottok i det siste trinnet (dens ClassLoader og en liste over dens grensesnitt), samt det tidligere opprettede InvocationHandler -objektet. Det viktigste er å ikke glemme å sende vårt originale arnoldobjekt til invokasjonsbehandleren, ellers blir det ingenting å "håndtere" :) Hva endte vi opp med? Vi har nå et proxy-objekt: proxyArnold . Den kan kalle alle metoder for Person- grensesnittet. Hvorfor? Fordi vi ga den en liste over alle grensesnittene 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));
Nå vet den om alle metodene til Person- grensesnittet. I tillegg sendte vi til vår proxy et PersonInvocationHandler -objekt konfigurert til å fungere med arnold- objektet:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Hvis vi nå kaller noen metode for Person- grensesnittet på proxy-objektet, avskjærer behandleren vår samtalen og utfører sin egen invoke()- metode i stedet. La oss prøve å kjøre main()- metoden! Konsoll utgang:

Hi!
Utmerket! Vi ser at i stedet for den opprinnelige Person.introduce()- metoden, kalles invoke()- metoden til vår PersonInvocationHandler() :

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

   System.out.println("Hi!");
   return null;
}
"Hei!" vises på konsollen, men dette er ikke akkurat den oppførselen vi ønsket :/ Det vi prøvde å oppnå er å først vise "Hei!" og deretter kalle selve den opprinnelige metoden. Med andre ord, metodekallet

proxyArnold.introduce(arnold.getName());
skal vise "Hei! Mitt navn er Arnold", ikke bare "Hei!" Hvordan kan vi oppnå dette? Det er ikke komplisert: vi trenger bare å ta noen friheter med vår behandler og invoke () -metoden :) Vær oppmerksom på hvilke argumenter som sendes til denne metoden:

public Object invoke(Object proxy, Method method, Object[] args)
Metoden invoke() har tilgang til den opprinnelig påkalte metoden, og til alle dens argumenter (Method method, Object[] args). Med andre ord, hvis vi kaller proxyArnold.introduce(arnold.getName())- metoden slik at invoke() -metoden kalles i stedet for introduce()- metoden, så har vi i denne metoden tilgang til den originale introduce()- metoden og dets argument! Som et resultat kan vi gjø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);
   }
}
Nå i invoke() -metoden har vi lagt til et kall til den opprinnelige metoden. Hvis vi nå prøver å kjøre koden fra vårt forrige 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å får vi se at nå fungerer alt som det skal :) Konsoll utgang:

Hi! My name is Arnold
Når kan du trenge dette? Egentlig ganske ofte. Designmønsteret "dynamisk proxy" brukes aktivt i populære teknologier... Å, forresten, jeg glemte å nevne at Dynamic Proxy er et designmønster! Gratulerer, du lærte en til! :) Dynamiske proxyer - 3For eksempel brukes det aktivt i populære teknologier og rammeverk knyttet til sikkerhet. Tenk deg at du har 20 metoder som bare skal utføres av brukere som er logget på programmet ditt. Ved å bruke teknikkene du har lært, kan du enkelt legge til disse 20 metodene en sjekk for å se om brukeren har skrevet inn gyldig legitimasjon uten å duplisere bekreftelseskoden i hver metode. Eller anta at du vil opprette en logg der alle brukerhandlinger vil bli registrert. Dette er også enkelt å gjøre ved å bruke en proxy. Selv nå kan du ganske enkelt legge til kode i eksemplet ovenfor slik at metodenavnet vises når du kaller invoke() , og det vil produsere en superenkel logg over programmet vårt :) Avslutningsvis, vær oppmerksom på en viktig begrensning. Et proxy-objekt fungerer med grensesnitt, ikke klasser. En proxy opprettes for et grensesnitt. Ta en titt på denne koden:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Her lager vi en proxy spesifikt for Person- grensesnittet. Hvis vi prøver å lage en proxy for klassen, dvs. endre type referanse og prøver å caste til Man- klassen, vil det ikke fungere.

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

proxyArnold.introduce(arnold.getName());
Unntak i tråden "hoved" java.lang.ClassCastException: com.sun.proxy.$Proxy0 kan ikke castes til Man Å ha et grensesnitt er et absolutt krav. Proxyer fungerer med grensesnitt. Det var alt for i dag :) Vel, nå ville det vært greit å løse noen få oppgaver! :) Til neste gang!
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION