CodeGym /Java blogg /Slumpmässig /Dynamiska proxyservrar i Java
John Squirrels
Nivå
San Francisco

Dynamiska proxyservrar i Java

Publicerad i gruppen
Hej! Idag kommer vi att överväga ett ganska viktigt och intressant ämne: skapandet av dynamiska proxyklasser i Java. Det är inte särskilt enkelt, så vi ska försöka ta reda på det med hjälp av exempel :) Så, den viktigaste frågan: vad är dynamiska proxyservrar och vad är de till för? En proxyklass är ett slags "tillägg" ovanpå originalklassen, vilket gör att vi kan ändra originalklassens beteende vid behov. Vad innebär det att "ändra beteende" och hur fungerar det? Tänk på ett enkelt exempel. Anta att vi har ett Person- gränssnitt och en enkel Man -klass som implementerar detta gränssnitt

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.
}
Vår Man -klass har 3 metoder: introducera, sayAge och sayWhereFrom. Föreställ dig att vi fick den här klassen som en del av ett färdigt JAR-bibliotek och vi kan inte bara skriva om dess kod. Men vi måste också ändra dess beteende. Vi vet till exempel inte vilken metod som kan anropas på vårt objekt, men vi vill att vår person ska säga "Hej!" (ingen gillar någon som är oartig) när någon av metoderna kallas. Dynamiska proxyservrar - 2Vad ska vi göra i den här situationen? Vi behöver några saker:
  1. InvocationHandler

Vad är detta? InvocationHandler är ett speciellt gränssnitt som låter oss fånga upp alla metodanrop till vårt objekt och lägga till det ytterligare beteende vi behöver. Vi behöver skapa vår egen interceptor, dvs skapa en klass som implementerar detta gränssnitt. Det här är ganska 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 behöver bara implementera en gränssnittsmetod: invoke() . Och, förresten, den gör vad vi behöver: den fångar upp alla metodanrop till vårt objekt och lägger till det nödvändiga beteendet (inuti metoden invoke() matar vi "Hej!" till konsolen).
  1. Det ursprungliga objektet och dess fullmakter.
Vi skapar vårt ursprungliga Man- objekt och ett "tillägg" (proxy) för 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 här ser inte särskilt enkelt ut! Jag har specifikt lagt till en kommentar för varje kodrad. Låt oss ta en närmare titt på vad som händer. På den första raden gör vi helt enkelt det ursprungliga objektet som vi ska skapa proxyservrar för. Följande två rader kan orsaka problem:

 // 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 händer faktiskt inget speciellt här :) På den fjärde raden använder vi den speciella Proxy- klassen och dess statiska newProxyInstance()- metod:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Denna metod skapar bara vårt proxyobjekt. Vi skickar till metoden informationen om den ursprungliga klassen, som vi fick i det sista steget (dess ClassLoader och en lista över dess gränssnitt), såväl som det tidigare skapade InvocationHandler -objektet. Huvudsaken är att inte glömma att skicka vårt ursprungliga arnoldobjekt till anropshanteraren, annars blir det inget att "hantera" :) Vad slutade vi med? Vi har nu ett proxyobjekt: proxyArnold . Det kan anropa alla metoder för Person -gränssnittet. Varför? Eftersom vi gav den en lista över alla gränssnitt här:

// 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 känner den till alla metoder för Person -gränssnittet. Dessutom skickade vi till vår proxy ett PersonInvocationHandler -objekt konfigurerat för att fungera med arnold -objektet:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Om vi ​​nu anropar någon metod för Person- gränssnittet på proxyobjektet, avlyssnar vår hanterare anropet och kör sin egen invoke()- metod istället. Låt oss försöka köra main() -metoden! Konsolutgång:

Hi!
Excellent! Vi ser att istället för den ursprungliga metoden Person.introduce() kallas invoke()- metoden för vår PersonInvocationHandler() :

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

   System.out.println("Hi!");
   return null;
}
"Hej!" visas på konsolen, men det här är inte exakt det beteende vi ville ha :/ Det vi försökte uppnå är att först visa "Hej!" och anropa sedan själva den ursprungliga metoden. Med andra ord, metodanropet

proxyArnold.introduce(arnold.getName());
ska visa "Hej! Jag heter Arnold", inte bara "Hej!" Hur kan vi uppnå detta? Det är inte komplicerat: vi behöver bara ta lite friheter med vår hanterare och metoden invoke() :) Var uppmärksam på vilka argument som skickas till denna metod:

public Object invoke(Object proxy, Method method, Object[] args)
Metoden invoke() har tillgång till den ursprungligen anropade metoden och till alla dess argument (Method method, Object[] args). Med andra ord, om vi anropar proxyArnold.introduce(arnold.getName()) -metoden så att invoke() -metoden anropas istället för introduce()- metoden, så har vi inuti denna metod tillgång till den ursprungliga introduce()- metoden och dess argument! Som ett resultat kan vi göra detta:

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 metoden invoke() har vi lagt till ett anrop till den ursprungliga metoden. Om vi ​​nu försöker köra koden från vårt tidigare exempel:

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());
   }
}
då får vi se att nu fungerar allt som det ska :) Konsolutgång:

Hi! My name is Arnold
När kan du behöva detta? Faktiskt ganska ofta. Designmönstret "dynamisk proxy" används aktivt i populära teknologier... Åh, förresten, jag glömde nämna att Dynamic Proxy är ett designmönster! Grattis, du lärde dig en till! :) Dynamiska proxyservrar - 3Till exempel används det aktivt i populära tekniker och ramverk relaterade till säkerhet. Föreställ dig att du har 20 metoder som endast bör köras av användare som är inloggade på ditt program. Med hjälp av de tekniker du har lärt dig kan du enkelt lägga till dessa 20 metoder en kontroll för att se om användaren har angett giltiga referenser utan att duplicera verifieringskoden i varje metod. Eller anta att du vill skapa en logg där alla användaråtgärder kommer att registreras. Detta är också enkelt att göra med en proxy. Redan nu kan du helt enkelt lägga till kod i vårt exempel ovan så att metodnamnet visas när du anropar invoke() , och det skulle producera en superenkel logg över vårt program :) Avslutningsvis, var uppmärksam på en viktig begränsning. Ett proxyobjekt fungerar med gränssnitt, inte klasser. En proxy skapas för ett gränssnitt. Ta en titt på den här koden:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Här skapar vi en proxy specifikt för Person- gränssnittet. Om vi ​​försöker skapa en proxy för klassen, dvs ändra typ av referens och försöker casta till klassen Man så fungerar det inte.

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

proxyArnold.introduce(arnold.getName());
Undantag i tråden "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 kan inte castas till Man Att ha ett gränssnitt är ett absolut krav. Proxyer fungerar med gränssnitt. Det var allt för idag :) Nåväl, nu skulle det vara bra att lösa några uppgifter! :) Tills nästa gång!
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION