ہیلو! آج ہم ایک اہم اور دلچسپ موضوع پر غور کریں گے - جاوا میں متحرک پراکسی کلاسز بنانا۔ یہ بہت آسان نہیں ہے، تو آئیے مثالوں سے اسے سمجھنے کی کوشش کریں :) تو، سب سے اہم سوال: متحرک پراکسیز کیا ہیں اور وہ کس لیے ہیں؟ ایک پراکسی کلاس اصل کلاس پر ایک قسم کا "سپر اسٹرکچر" ہے، جو ہمیں ضرورت پڑنے پر اس کے رویے کو تبدیل کرنے کی اجازت دیتا ہے۔ رویے کو تبدیل کرنے کا کیا مطلب ہے اور یہ کیسے کام کرتا ہے؟ آئیے ایک سادہ سی مثال دیکھتے ہیں۔ ہم کہتے ہیں کہ ہمارے پاس ایک انٹرفیس
Person
اور ایک سادہ کلاس ہے Man
جو اس انٹرفیس کو نافذ کرتی ہے۔
public interface Person {
public void introduce(String name);
public void sayAge(int age);
public void sayFrom(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("Меня зовут " + this.name);
}
@Override
public void sayAge(int age) {
System.out.println("Мне " + this.age + " years");
}
@Override
public void sayFrom(String city, String country) {
System.out.println("Я из города " + this.city + ", " + this.country);
}
//..геттеры, сеттеры, и т.д.
}
ہماری کلاس کے Man
3 طریقے ہیں: اپنا تعارف کروائیں، اپنی عمر بتائیں، اور بتائیں کہ آپ کہاں سے ہیں۔ آئیے تصور کریں کہ ہم نے یہ کلاس ایک ریڈی میڈ JAR لائبریری کے حصے کے طور پر حاصل کی ہے اور ہم اس کا کوڈ لے کر دوبارہ لکھ نہیں سکتے۔ تاہم، ہمیں اس کے رویے کو تبدیل کرنے کی ضرورت ہے. مثال کے طور پر، ہم نہیں جانتے کہ ہمارے اعتراض پر کس طریقہ کو پکارا جائے گا، اور اس لیے ہم چاہتے ہیں کہ ان میں سے کسی کو کال کرتے وقت وہ شخص پہلے "ہیلو!" کہے۔ (کوئی بھی ایسے شخص کو پسند نہیں کرتا جو بدتمیز ہو)۔ ایسی حالت میں ہمیں کیا کرنا چاہیے؟ ہمیں چند چیزوں کی ضرورت ہوگی:
-
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("Hello!");
return null;
}
}
ہمیں صرف ایک انٹرفیس طریقہ کو لاگو کرنے کی ضرورت ہے invoke()
۔ یہ، درحقیقت، وہی کرتا ہے جس کی ہمیں ضرورت ہوتی ہے - یہ ہمارے آبجیکٹ میں تمام میتھڈ کالز کو روکتا ہے اور ضروری رویے کا اضافہ کرتا ہے (یہاں ہم invoke()
طریقہ کے اندر کنسول پر "Hello!" پرنٹ کرتے ہیں)۔
- اصل آبجیکٹ اور اس کی پراکسی۔
Man
اس کے لیے ایک اصل چیز اور ایک "سپر اسٹرکچر" (پراکسی) بنائیں:
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
//Создаем оригинальный an object
Man vasia = new Man("Vasya", 30, "Санкт-Петербург", "Россия");
//Получаем загрузчик класса у оригинального an object
ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();
//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
//Вызываем у прокси an object один из методов нашего оригинального an object
proxyVasia.introduce(vasia.getName());
}
}
بہت سادہ نہیں لگتا! میں نے خاص طور پر کوڈ کی ہر سطر کے لیے ایک تبصرہ لکھا: آئیے قریب سے دیکھیں کہ وہاں کیا ہو رہا ہے۔
پہلی لائن میں ہم صرف اصل آبجیکٹ بناتے ہیں جس کے لیے ہم ایک پراکسی بنائیں گے۔ درج ذیل دو سطریں آپ کو الجھن کا باعث بن سکتی ہیں۔
//Получаем загрузчик класса у оригинального an object
ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();
//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();
لیکن یہاں واقعی کچھ خاص نہیں ہو رہا ہے :) ایک پراکسی بنانے کے لیے، ہمیں ClassLoader
اصل آبجیکٹ کے (کلاس لوڈر) اور ان تمام انٹرفیس کی فہرست کی ضرورت ہے جو ہماری اصل کلاس (یعنی Man
) لاگو کرتی ہے۔ اگر آپ نہیں جانتے کہ یہ کیا ہے ClassLoader
، تو آپ JVM میں کلاسز لوڈ کرنے کے بارے میں یا Habré پر اس مضمون کو پڑھ سکتے ہیں ، لیکن ابھی تک اس سے زیادہ پریشان نہ ہوں۔ بس یاد رکھیں کہ ہمیں تھوڑی اضافی معلومات مل رہی ہیں جس کے بعد ہمیں پراکسی آبجیکٹ بنانے کی ضرورت ہوگی۔ چوتھی لائن پر ہم ایک خاص کلاس اور اس کا جامد طریقہ استعمال کرتے ہیں : Proxy
newProxyInstance()
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Этот метод How раз создает наш прокси-an object. В метод мы передаем ту информацию об оригинальном классе, которую получor на прошлом шаге (его ClassLoader
и список его интерфейсов), а также an object созданного нами ранее перехватчика — InvocationHandler
’a. Главное — не забудь передать перехватчику наш оригинальный an object vasia
, иначе ему нечего будет «перехватывать» :) What же у нас в итоге получилось? У нас теперь есть прокси-an object vasiaProxy
. Он может вызывать любые методы интерфейса Person
. Почему? Потому что мы передали ему список всех интерфейсов — вот здесь:
//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Теперь он «в курсе» всех методов интерфейса Person
. Кроме того, мы передали нашему прокси an object PersonInvocationHandler
, настроенный на работу с an objectом vasia
:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Теперь, если мы вызовем у прокси-an object любой метод интерфейса Person
, наш перехватчик «словит» этот вызов и выполнит instead of него свой метод invoke()
. Давай попробуем запустить метод main()
! Вывод в консоль: Hello! Отлично! Мы видим, что instead of настоящего метода Person.introduce()
вызван метод invoke()
нашего PersonInvocationHandler()
:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Hello!");
return null;
}
И в консоль было выведено «Hello!» Но это не совсем то поведение, которое мы хотели получить :/ По нашей задумке сначала должно быть выведено «Hello!», а после — сработать сам метод, который мы вызываем. Иными словами, вот этот вызов метода:
proxyVasia.introduce(vasia.getName());
должен выводить в консоль «Hello! Меня зовут Вася», а не просто «Hello!» Как же нам добиться этого? Ничего сложного: просто придется немного похимичить над нашим перехватчиком и методом invoke()
:) Обрати внимание, Howие аргументы передаются в этот метод:
public Object invoke(Object proxy, Method method, Object[] args)
У метода invoke()
есть доступ к методу, instead of которого он вызывается, и ко всем его аргументам (Method method, Object[] args). Иными словами, если мы вызываем метод proxyVasia.introduce(vasia.getName())
, и instead of метода introduce()
вызывается метод invoke()
, внутри этого метода у нас есть доступ и к оригинальному методу introduce()
, и к его аргументу! В результате мы можем сделать вот так:
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("Hello!");
return method.invoke(person, args);
}
}
Теперь мы добавor в метод invoke()
вызов оригинального метода. Если мы попробуем сейчас запустить code из нашего предыдущего примера:
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
//Создаем оригинальный an object
Man vasia = new Man("Vasya", 30, "Санкт-Петербург", "Россия");
//Получаем загрузчик класса у оригинального an object
ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();
//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
//Вызываем у прокси an object один из методов нашего оригинального an object
proxyVasia.introduce(vasia.getName());
}
}
то увидим, что теперь все работает How надо :) Вывод в консоль: Hello! Меня зовут Вася Где это может тебе понадобиться? На самом деле, много где. Паттерн проектирования «динамический прокси» активно используется в популярных технологиях...а я, кстати, и забыл тебе сказать, что Dynamic Proxy
— это паттерн! Поздравляю, ты выучил еще один! :) Так вот, он активно используется в популярных технологиях и фреймворках, связанных с безопасностью. Представь, что у тебя есть 20 методов, которые могут выполнять только залогиненные пользователи твоей программы. С помощью изученных приемов ты легко сможешь добавить в эти 20 методов проверку того, ввел ли пользователь логин и пароль, не дублируя code проверки отдельно в каждом методе. Или, к примеру, если ты хочешь создать журнал, куда будут записываться все действия пользователей, это также легко сделать с использованием прокси. Можно даже сейчас: просто допиши в пример code, чтобы название метода выводилось в консоль при вызове invoke()
, и ты получишь простенький журнал логов нашей программы :) В завершение лекции, обрати внимание на одно важное ограничение. Creation прокси an object происходит на уровне интерфейсов, а не классов. Прокси создается для интерфейса. Взгляни на этот code:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Здесь мы создаем прокси именно для интерфейса Person
. Если попробуем создать прокси для класса, то есть поменяем тип ссылки и попытаемся сделать приведение к классу Man
, у нас ничего не выйдет.
Man proxyVasia = (Man) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
proxyVasia.introduce(vasia.getName());
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to Man Наличие интерфейса — обязательное требование. Прокси работает на уровне интерфейсов. На этом на сегодня все :) В качестве дополнительного материала по теме прокси могу порекомендовать тебе отличное видео, и также неплохую статью. Ну, а теперь было бы неплохо решить несколько задач! :) До встречи!
GO TO FULL VERSION