JavaRush /مدونة جافا /Random-AR /الوكلاء الديناميكيون في جافا

الوكلاء الديناميكيون في جافا

نشرت في المجموعة
مرحبًا! سننظر اليوم إلى موضوع مهم ومثير للاهتمام إلى حد ما - إنشاء فئات وكيل ديناميكية في Java. الأمر ليس بسيطًا للغاية، لذا دعونا نحاول معرفة ذلك بالأمثلة :) لذا، السؤال الأكثر أهمية: ما هي الوكلاء الديناميكيون وما الغرض منها؟ تعتبر فئة الوكيل نوعًا من "البنية الفوقية" فوق الفئة الأصلية، مما يسمح لنا بتغيير سلوكها إذا لزم الأمر. ماذا يعني تغيير السلوك وكيف يعمل؟ دعونا نلقي نظرة على مثال بسيط. لنفترض أن لدينا واجهة 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 جاهزة ولا يمكننا ببساطة أخذ الكود الخاص به وإعادة كتابته. ومع ذلك، علينا أن نغير سلوكه. على سبيل المثال، نحن لا نعرف الطريقة التي سيتم استدعاؤها على كائننا، وبالتالي نريد من الشخص أن يقول "مرحبًا" أولاً عند الاتصال بأي منها. (لا أحد يحب الشخص غير المهذب). الوكلاء الديناميكيون - 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()"مرحبًا!" إلى وحدة التحكم داخل الطريقة).
  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(محمل الفئة) للكائن الأصلي وقائمة بجميع الواجهات التي Manتنفذها فئتنا الأصلية (على سبيل المثال ). إذا كنت لا تعرف ما هو ClassLoader، يمكنك قراءة هذه المقالة حول تحميل الفئات في JVM أو هذه الموجودة على حبري ، لكن لا تزعج نفسك كثيرًا بها بعد. فقط تذكر أننا نحصل على القليل من المعلومات الإضافية التي سنحتاجها بعد ذلك لإنشاء كائن الوكيل. في السطر الرابع نستخدم فئة خاصة 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;
}
وعرضت وحدة التحكم "مرحبًا!" ولكن هذا ليس بالضبط السلوك الذي أردنا الحصول عليه:/ وفقًا لفكرتنا، يجب أولاً عرض "مرحبًا!"، ومن ثم يجب أن تعمل الطريقة نفسها التي نتصل بها. بمعنى آخر، تستدعي هذه الطريقة:
proxyVasia.introduce(vasia.getName());
يجب أن يخرج إلى وحدة التحكم "Hello! اسمي فاسيا"، وليس فقط "مرحبًا!". كيف نستطيع إنجاز هذا؟ لا يوجد شيء معقد: ما عليك سوى التلاعب قليلاً بالمعترض والطريقة invoke():) انتبه إلى الوسائط التي يتم تمريرها إلى هذه الطريقة:
public Object invoke(Object proxy, Method method, Object[] args)
تتمتع الطريقة invoke()بإمكانية الوصول إلى الطريقة التي يتم استدعاؤها بدلاً من ذلك وجميع الوسائط الخاصة بها (طريقة الطريقة، وسيطات Object[]). بمعنى آخر، إذا استدعينا طريقة 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 طريقة لا يمكن تنفيذها إلا من قبل المستخدمين الذين قاموا بتسجيل الدخول إلى برنامجك. باستخدام التقنيات التي تعلمتها، يمكنك بسهولة إضافة فحص إلى هذه الطرق العشرين لمعرفة ما إذا كان المستخدم قد أدخل معلومات تسجيل الدخول وكلمة المرور، دون تكرار رمز التحقق بشكل منفصل في كل طريقة. أو، على سبيل المثال، إذا كنت تريد إنشاء سجل حيث سيتم تسجيل جميع إجراءات المستخدم، فمن السهل أيضًا القيام بذلك باستخدام الوكيل. يمكنك حتى الآن: ما عليك سوى إضافة رمز إلى المثال بحيث يتم عرض اسم الطريقة في وحدة التحكم عند الاتصال بها 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 إلى الرجل. يعد وجود الواجهة متطلبًا إلزاميًا. يعمل الوكيل على مستوى الواجهة. هذا كل شيء لهذا اليوم :) كمواد إضافية حول موضوع الوكلاء، يمكنني أن أوصيكم بفيديو ممتاز ومقالة جيدة أيضًا. حسنا، الآن سيكون من الجميل حل بعض المشاكل! :) أرك لاحقًا!
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION