CodeGym /وبلاگ جاوا /Random-FA /پراکسی های پویا در جاوا
John Squirrels
مرحله
San Francisco

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

در گروه منتشر شد
سلام! امروز یک موضوع بسیار مهم و جالب را در نظر خواهیم گرفت: ایجاد کلاس های پروکسی پویا در جاوا. خیلی ساده نیست، بنابراین سعی می کنیم با استفاده از مثال ها آن را بفهمیم :) بنابراین، مهمترین سوال: پراکسی های پویا چیست و برای چیست؟ یک کلاس پراکسی نوعی "افزونه" در بالای کلاس اصلی است که به ما امکان می دهد در صورت لزوم رفتار کلاس اصلی را تغییر دهیم. "تغییر رفتار" به چه معناست و چگونه کار می کند؟ یک مثال ساده را در نظر بگیرید. فرض کنید یک رابط 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 متد دارد: install، sayAge و sayWhereFrom. تصور کنید که ما این کلاس را به عنوان بخشی از یک کتابخانه JAR خارج از قفسه دریافت کرده ایم و نمی توانیم به سادگی کد آن را بازنویسی کنیم. اما ما همچنین باید رفتار آن را تغییر دهیم. به عنوان مثال، ما نمی دانیم که کدام متد ممکن است روی شی ما فراخوانی شود، اما می خواهیم شخص ما بگوید "سلام!" (هیچ کس کسی را که بی ادب است دوست ندارد) وقتی هر یک از روش ها نامیده می شود. پراکسی های پویا - 2در این شرایط چه باید بکنیم؟ ما به چند چیز نیاز داریم:
  1. Invocation Handler

این چیه؟ 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());

   }
}
این خیلی ساده به نظر نمی رسد! من به طور خاص برای هر خط کد یک نظر اضافه کردم. بیایید نگاهی دقیق تر به آنچه در حال وقوع است بیندازیم. در خط اول، ما به سادگی شی اصلی را می سازیم که برای آن پروکسی ایجاد می کنیم. دو خط زیر ممکن است برای شما مشکل ایجاد کند:
// 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 و لیستی از رابط های آن) و همچنین شی InvocationHandler که قبلا ایجاد شده بود را به متد منتقل می کنیم . نکته اصلی این است که فراموش نکنیم که شی اصلی آرنولد خود را به کنترل کننده فراخوانی منتقل کنیم، در غیر این صورت چیزی برای "دست زدن" وجود نخواهد داشت :) در نهایت به چه چیزی رسیدیم؟ ما اکنون یک شی پراکسی داریم: proxyArnold . می تواند هر روشی از رابط 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 را به پروکسی خود ارسال کردیم که برای کار با شی آرنولد پیکربندی شده است :
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
حال اگر هر متدی از رابط Person را روی شی پراکسی فراخوانی کنیم، کنترل کننده ما تماس را قطع می کند و متد invoke() خود را به جای آن اجرا می کند. بیایید سعی کنیم متد main() را اجرا کنیم ! خروجی کنسول:

Hi!
عالی! می بینیم که به جای متد ()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());
باید "سلام! نام من آرنولد است"، نه فقط "سلام!" ما چگونه می توانیم به این دست پیدا کنیم؟ این پیچیده نیست: ما فقط باید کمی آزادی عمل با handler خود و متد invoke() داشته باشیم :) توجه کنید که چه آرگومان هایی به این متد ارسال می شود:
public Object invoke(Object proxy, Method method, Object[] args)
متد invoke () به متد اولیه فراخوانی شده و به همه آرگومان های آن (روش متد، Object[] args) دسترسی دارد. به عبارت دیگر، اگر متد proxyArnold.introduce(arnold.getName()) را فراخوانی کنیم تا متد invoke() به جای متد ()introduce فراخوانی شود، در داخل این متد به متد اصلی ()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("Hi!");
       return method.invoke(person, args);
   }
}
اکنون در متد invoke() یک فراخوانی به متد اصلی اضافه کرده ایم. اگر اکنون سعی کنیم کد مثال قبلی خود را اجرا کنیم:
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());
   }
}
سپس خواهیم دید که اکنون همه چیز همانطور که باید کار می کند :) خروجی کنسول:

Hi! My name is Arnold
چه زمانی ممکن است به این نیاز داشته باشید؟ در واقع، اغلب اوقات. الگوی طراحی "پروکسی پویا" به طور فعال در فناوری های محبوب استفاده می شود ... اوه، اتفاقاً یادم رفت بگم که Dynamic Proxy یک الگوی طراحی است! تبریک می گویم، شما یک چیز دیگر یاد گرفتید! :) پراکسی های پویا - 3به عنوان مثال، به طور فعال در فن آوری های محبوب و چارچوب های مرتبط با امنیت استفاده می شود. تصور کنید 20 روش دارید که فقط باید توسط کاربرانی که وارد برنامه شما شده اند اجرا شوند. با استفاده از تکنیک هایی که آموخته اید، می توانید به راحتی به این 20 روش یک بررسی اضافه کنید تا ببینید آیا کاربر اعتبارنامه های معتبری را بدون تکرار کد تأیید در هر روش وارد کرده است یا خیر. یا فرض کنید می خواهید یک گزارش ایجاد کنید که در آن تمام اقدامات کاربر ثبت شود. انجام این کار با استفاده از پروکسی نیز آسان است. حتی در حال حاضر، می توانید به سادگی کدی را به مثال بالا اضافه کنید تا نام متد هنگام فراخوانی invoke() نمایش داده شود و این یک گزارش فوق العاده ساده از برنامه ما ایجاد می کند :) در پایان، به یک محدودیت مهم توجه کنید. یک شی پراکسی با رابط ها کار می کند نه کلاس ها. یک پروکسی برای یک رابط ایجاد می شود. به این کد نگاهی بیندازید:
// 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());
استثنا در رشته "اصلی" java.lang.ClassCastException: com.sun.proxy.$Proxy0 را نمی توان به Man ارسال کرد داشتن یک رابط یک الزام مطلق است. پروکسی ها با رابط ها کار می کنند. همین برای امروز :) خب حالا خوبه چند تا کار حل کنیم! :) تا دفعه بعد!
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION