JavaRush /وبلاگ جاوا /Random-FA /پراکسی های پویا در جاوا

پراکسی های پویا در جاوا

در گروه منتشر شد
سلام! امروز ما به یک موضوع بسیار مهم و جالب نگاه خواهیم کرد - ایجاد کلاس های پروکسی پویا در جاوا. خیلی ساده نیست، پس بیایید سعی کنیم با مثال ها آن را بفهمیم :) بنابراین، مهمترین سوال: پراکسی های پویا چیست و برای چیست؟ یک کلاس پروکسی نوعی "روبنا" بر روی کلاس اصلی است که به ما امکان می دهد در صورت لزوم رفتار آن را تغییر دهیم. تغییر رفتار به چه معناست و چگونه کار می کند؟ بیایید به یک مثال ساده نگاه کنیم. 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);
   }

   //..геттеры, сеттеры, и т.д.
}
کلاس ما Man3 روش دارد: خودتان را معرفی کنید، سن خود را بگویید و بگویید اهل کجا هستید. بیایید تصور کنیم که ما این کلاس را به عنوان بخشی از یک کتابخانه آماده JAR دریافت کرده ایم و نمی توانیم به سادگی کد آن را بگیریم و بازنویسی کنیم. با این حال، ما باید رفتار او را تغییر دهیم. به عنوان مثال، ما نمی دانیم که کدام متد بر روی شی ما فراخوانی می شود، و بنابراین می خواهیم فرد هنگام فراخوانی هر یک از آنها ابتدا بگوید "Hello!". (هیچ کس کسی را که بی ادب است دوست ندارد). پراکسی های پویا - 1در چنین شرایطی چه کنیم؟ ما به چند چیز نیاز خواهیم داشت:
  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("Hello!");
       return null;
   }
}
ما باید تنها یک روش رابط را پیاده سازی کنیم - invoke(). در واقع آنچه را که ما نیاز داریم انجام می دهد - همه فراخوانی های متد را به شی ما قطع می کند و رفتار لازم را اضافه می کند (در اینجا ما invoke()"Hello!" را به کنسول داخل متد چاپ می کنیم).
  1. شی اصلی و پروکسی آن.
بیایید یک شی اصلی 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(clas loader) شی اصلی و لیستی از تمام رابط هایی که کلاس اصلی ما (یعنی Man) پیاده سازی می کند نیاز داریم. اگر نمی‌دانید چیست ClassLoader، می‌توانید این مقاله را درباره بارگیری کلاس‌ها در JVM یا این مقاله در Habré بخوانید ، اما هنوز زیاد به آن زحمت ندهید. فقط به یاد داشته باشید که ما کمی اطلاعات اضافی دریافت می کنیم که پس از آن برای ایجاد شی پراکسی به آن نیاز خواهیم داشت. در خط چهارم از یک کلاس خاص Proxyو روش استاتیک آن استفاده می کنیم newProxyInstance():
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
این روش فقط شی پراکسی ما را ایجاد می کند. به متد، اطلاعات مربوط به کلاس اصلی را که در مرحله قبل دریافت کردیم (آن ClassLoaderو لیست رابط های آن)، و همچنین شی رهگیر که قبلا ایجاد کردیم - InvocationHandler'a' منتقل می کنیم. نکته اصلی این است که فراموش نکنید جسم اصلی خود را به رهگیر منتقل کنید vasia، در غیر این صورت چیزی برای "رهگیری" نخواهد داشت :) در نهایت به چه چیزی رسیدیم؟ ما اکنون یک شیء پروکسی داریم vasiaProxy. این می تواند هر روش رابط راPerson فراخوانی کند . چرا؟ زیرا ما لیستی از تمام رابط ها را به آن ارسال کردیم - در اینجا:
//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();

//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
اکنون او از تمام روش های رابط "آگاه است" Person. علاوه بر این، ما به پروکسی خود یک شی را ارسال کردیم PersonInvocationHandlerکه برای کار با شی پیکربندی شده است vasia:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
حال، اگر هر متد واسطی را در شیء پراکسی فراخوانی کنیم Person، رهگیر ما این فراخوانی را "گرفته" و به جای آن متد خود را اجرا می کند invoke(). بیایید سعی کنیم روش را اجرا کنیم main()! خروجی کنسول: سلام! عالی! می بینیم که به جای روش واقعی، 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! نام من واسیا است، و نه فقط "سلام!" ما چگونه می توانیم به این دست پیدا کنیم؟ هیچ چیز پیچیده ای نیست: فقط باید کمی با رهگیر و روش ما سر و صدا کنید invoke():) توجه کنید که چه آرگومان هایی به این روش منتقل می شود:
public Object invoke(Object proxy, Method method, Object[] args)
یک متد invoke()به جای آن به متدی که فراخوانی می‌شود و همه آرگومان‌های آن (روش متد، Object[] args) دسترسی دارد. به عبارت دیگر، اگر یک متد را فراخوانی کنیم proxyVasia.introduce(vasia.getName())و به جای متد ، 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);
   }
}
invoke()اکنون یک فراخوانی به متد اصلی به متد اضافه کرده ایم . اگر اکنون سعی کنیم کد مثال قبلی خود را اجرا کنیم:
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());
   }
}
سپس خواهیم دید که اکنون همه چیز همانطور که باید کار می کند :) خروجی کنسول: سلام! نام من واسیا است. کجا ممکن است به آن نیاز داشته باشید؟ در واقع، بسیاری از مکان ها. الگوی طراحی "پروکسی پویا" به طور فعال در فناوری های محبوب استفاده می شود ... و اتفاقاً، فراموش کردم به شما بگویم که Dynamic Proxyیک الگو است ! تبریک می گویم، شما یکی دیگر را یاد گرفتید! :) پراکسی های پویا - 2بنابراین، به طور فعال در فن آوری های محبوب و چارچوب های مرتبط با امنیت استفاده می شود. تصور کنید که شما 20 روش دارید که فقط توسط کاربرانی که وارد برنامه شما شده اند قابل اجرا هستند. با استفاده از تکنیک هایی که آموخته اید، می توانید به راحتی به این 20 روش یک بررسی اضافه کنید تا ببینید آیا کاربر لاگین و رمز عبور را وارد کرده است، بدون اینکه کد تایید را به طور جداگانه در هر روش تکرار کنید. یا، برای مثال، اگر می خواهید یک گزارش ایجاد کنید که در آن تمام اقدامات کاربر ثبت شود، این کار نیز با استفاده از یک پروکسی آسان است. در حال حاضر نیز امکان پذیر است: فقط کدی را به مثال اضافه کنید تا در هنگام فراخوانی نام روش در کنسول نمایش داده شود invoke()و یک گزارش ساده از برنامه ما دریافت کنید :) در پایان سخنرانی به یک نکته مهم توجه کنید. محدودیت . ایجاد یک شیء پراکسی در سطح رابط اتفاق می افتد، نه در سطح کلاس. یک پروکسی برای رابط ایجاد می شود. به این کد دقت کنید:
//Создаем прокси нашего 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());
استثنا در رشته "اصلی" java.lang.ClassCastException: com.sun.proxy.$Proxy0 را نمی توان به Man فرستاد. وجود یک رابط یک الزام اجباری است. پروکسی در سطح رابط کار می کند. این همه برای امروز است :) به عنوان مطالب اضافی در مورد موضوع پروکسی ها، می توانم یک ویدیوی عالی و همچنین یک مقاله خوب. خوب، حالا خوب است که چند مشکل را حل کنیم! :) به امید دیدار!
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION