CodeGym /Java блог /Случаен /Динамични проксита в Java
John Squirrels
Ниво
San Francisco

Динамични проксита в Java

Публикувано в групата
здрасти Днес ще разгледаме доста важна и интересна тема: създаването на динамични прокси класове в Java. Не е много просто, така че ще се опитаме да го разберем с помощта на примери :) И така, най-важният въпрос: Howво представляват динамичните проксита и за Howво служат? Прокси класът е вид „добавка“ върху оригиналния клас, която ни позволява да променим поведението на оригиналния клас, ако е необходимо. Какво означава „промяна на поведението“ и How работи това? Помислете за един прост пример. Да предположим, че имаме интерфейс Person и прост клас Man , който имплементира този интерфейс

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 има 3 метода: uvod, sayAge и sayWhereFrom. Представете си, че имаме този клас като част от готова JAR библиотека и не можем просто да пренапишем нейния code. Но също така трябва да променим поведението му. Например, ние не знаем кой метод може да бъде извикан на нашия обект, но искаме нашият човек да каже "Здравей!" (никой не харесва някой, който е неучтив), когато се извика някой от методите. Динамични проксита - 2Какво да правим в тази ситуация? Ще ни трябват няколко неща:
  1. InvocationHandler

Какво е това? InvocationHandler е специален интерфейс, който ни позволява да прихванем всяко извикване на метод към нашия обект и да добавим допълнителното поведение, от което се нуждаем. Трябва да създадем наш собствен интерцептор, т.е. да създадем клас, който имплементира този интерфейс. Това е доста просто:

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;
   }
}
Трябва да внедрим само един интерфейсен метод: invoke() . И, между другото, прави това, от което се нуждаем: прихваща всички извиквания на метод към нашия обект и добавя необходимото поведение (вътре в метода invoke() извеждаме „Hi!“ към конзолата).
  1. Оригиналният обект и неговите проксита.
Ние създаваме нашия оригинален обект Man и "добавка" (прокси) за него:

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

   }
}
Това не изглежда много просто! Специално добавих коментар за всеки ред code. Нека да разгледаме по-отблизо Howво се случва. В първия ред просто създаваме оригиналния обект, за който ще създадем проксита. Следните два реда може да ви затруднят:

 // 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();
Всъщност тук не се случва нищо наистина специално :) В четвъртия ред използваме специалния клас Proxy и неговия статичен метод newProxyInstance() :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Този метод просто създава нашия прокси обект. Предаваме на метода информацията за оригиналния клас, който получихме в последната стъпка (неговия ClassLoader и списък с неговите интерфейси), Howто и предварително създадения обект InvocationHandler . Основното нещо е да не забравяме да предадем нашия оригинален обект arnold към манипулатора на извикване, в противен случай няма да има Howво да се "обработва" :) Какво получихме в крайна сметка? Вече имаме прокси обект: proxyArnold . Може да извиква всяHowви методи на интерфейса Person . Защо? Защото ние му дадохме списък с всички интерфейси тук:

// 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));
Сега той знае за всички методи на интерфейса Person . В допълнение, ние предадохме на нашия прокси обект PersonInvocationHandler , конфигуриран да работи с обекта arnold :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Сега, ако извикаме който и да е метод на интерфейса Person на прокси обекта, нашият манипулатор прихваща извикването и instead of това изпълнява свой собствен метод invoke() . Нека се опитаме да изпълним метода main() ! Конзолен изход:

Hi!
Отлично! Виждаме, че instead of оригиналния метод Person.introduce() се извиква методът invoke() на нашия PersonInvocationHandler() :

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

   System.out.println("Hi!");
   return null;
}
"Здрасти!" се показва на конзолата, но това не е точно поведението, което искахме :/ Това, което се опитвахме да постигнем, е първо да покажем „Здрасти!“ и след това извикване на самия оригинален метод.С други думи извикването на метода

proxyArnold.introduce(arnold.getName());
трябва да показва "Здравей! Името ми е Арнолд", а не просто "Здравей!" Как можем да постигнем това? Не е сложно: просто трябва да си позволим малко свободи с нашия манипулатор и метода invoke() :) Обърнете внимание Howви аргументи се предават на този метод:

public Object invoke(Object proxy, Method method, Object[] args)
Методът invoke() има достъп до първоначално извикания метод и до всички негови аргументи (метод на метода, аргументи на обект []). С други думи, ако извикаме метода proxyArnold.introduce(arnold.getName()) , така че методът invoke() да бъде извикан instead of метода uvod() , тогава вътре в този метод имаме достъп до оригиналния метод uvod() и неговия аргумент! В резултат на това можем да направим следното:

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);
   }
}
Сега в метода invoke() добавихме извикване към оригиналния метод. Ако сега се опитаме да изпълним codeа от предишния ни пример:

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());
   }
}
тогава ще видим, че сега всичко работи Howто трябва :) Конзолен изход:

Hi! My name is Arnold
Кога може да имате нужда от това? Всъщност доста често. Шаблонът за проектиране "динамичен прокси" се използва активно в популярните технологии... О, между другото, забравих да спомена, че Dynamic Proxy е шаблон за проектиране! Поздравления, научихте още нещо! :) Динамични проксита - 3Например, той се използва активно в популярни технологии и рамки, свързани със сигурността. Представете си, че имате 20 метода, които трябва да се изпълняват само от потребители, които са влезли във вашата програма. Използвайки техниките, които сте научor, можете лесно да добавите към тези 20 метода проверка, за да видите дали потребителят е въвел валидни идентификационни данни, без да дублирате codeа за потвърждение във всеки метод. Или да предположим, че искате да създадете дневник, в който ще се записват всички потребителски действия. Това също е лесно да се направи с помощта на прокси. Дори сега можете просто да добавите code към нашия пример по-горе, така че името на метода да се показва, когато извиквате invoke() и това ще създаде супер прост журнал на нашата програма :) В заключение, обърнете внимание на едно важно ограничение. Прокси обект работи с интерфейси, а не с класове. Създава се прокси за интерфейс. Разгледайте този code:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Тук създаваме прокси специално за интерфейса Person . Ако се опитаме да създадем прокси за класа, т.е. променим типа на препратката и се опитаме да кастнем към класа Man , няма да работи.

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

proxyArnold.introduce(arnold.getName());
Изключение в нишката "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 не може да бъде прехвърлено към Man Наличието на интерфейс е абсолютно изискване. Прокситата работят с интерфейси. Това е всичко за днес :) Е, сега би било добре да решите няколко задачи! :) До следващия път!
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION