أهلاً! اليوم سننظر في موضوع مهم ومثير للاهتمام إلى حد ما: إنشاء فئات الوكيل الديناميكية في Java. الأمر ليس بسيطًا جدًا، لذا سنحاول معرفة ذلك باستخدام الأمثلة :) لذا، السؤال الأكثر أهمية: ما هي الوكلاء الديناميكيون وما الغرض منها؟ فئة الوكيل هي نوع من "الوظيفة الإضافية" أعلى الفئة الأصلية، والتي تسمح لنا بتغيير سلوك الفئة الأصلية إذا لزم الأمر. ماذا يعني "تغيير السلوك" وكيف يعمل ذلك؟ النظر في مثال بسيط. لنفترض أن لدينا واجهة شخص وفئة رجل بسيطة تنفذ هذه الواجهة
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.
}
تحتوي فئة الرجال لدينا على ثلاث طرق: تقديم، وsayAge، وsayWhereFrom. تخيل أننا حصلنا على هذا الفصل كجزء من مكتبة JAR جاهزة للاستخدام ولا يمكننا ببساطة إعادة كتابة الكود الخاص بها. ولكننا نحتاج أيضًا إلى تغيير سلوكها. على سبيل المثال، لا نعرف الطريقة التي يمكن استدعاؤها على كائننا، ولكننا نريد من شخصنا أن يقول "مرحبًا!" (لا أحد يحب شخصًا غير مهذب) عند استدعاء أي من الأساليب. ماذا يجب أن نفعل في هذه الحالة؟ سنحتاج إلى بعض الأشياء:
-
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;
}
}
نحتاج إلى تنفيذ طريقة واجهة واحدة فقط: استدعاء () . وبالمناسبة، فهو يفعل ما نحتاجه: فهو يعترض جميع استدعاءات الأساليب للكائن الخاص بنا ويضيف السلوك الضروري (داخل طريقة الاستدعاء () ، نخرج "Hi!" إلى وحدة التحكم).
- الكائن الأصلي ووكلاءه.
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 . يمكنه استدعاء أي طرق من واجهة الشخص . لماذا؟ لأننا قدمنا لها قائمة بجميع الواجهات هنا:
// 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));
الآن أصبح يعرف جميع أساليب واجهة الشخص . بالإضافة إلى ذلك، مررنا إلى وكيلنا كائن PersonInvocationHandler الذي تم تكوينه للعمل مع كائن arnold :
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
الآن، إذا قمنا باستدعاء أي طريقة من واجهة الشخص على كائن الوكيل، فسيعترض معالجنا المكالمة وينفذ طريقة الاستدعاء () الخاصة به بدلاً من ذلك. دعونا نحاول تشغيل الطريقة الرئيسية () ! إخراج وحدة التحكم:
Hi!
ممتاز! نرى أنه بدلاً من طريقة Person.introduce() الأصلية ، يتم استدعاء طريقة invocation() الخاصة بـ PersonInvocationHandler() الخاصة بنا:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Hi!");
return null;
}
"أهلاً!" يتم عرضه على وحدة التحكم، ولكن هذا ليس هو السلوك الذي أردناه بالضبط :/ ما كنا نحاول تحقيقه هو عرض "مرحبًا!" ثم استدعاء الطريقة الأصلية نفسها، وبعبارة أخرى، استدعاء الطريقة
proxyArnold.introduce(arnold.getName());
يجب أن يعرض "مرحبًا! اسمي أرنولد"، وليس مجرد "مرحبًا!" كيف نستطيع إنجاز هذا؟ الأمر ليس معقدًا: نحتاج فقط إلى أخذ بعض الحرية مع معالجنا وطريقة الاستدعاء () :) انتبه إلى الوسائط التي يتم تمريرها إلى هذه الطريقة:
public Object invoke(Object proxy, Method method, Object[] args)
يتمتع أسلوب الاستدعاء () بإمكانية الوصول إلى الأسلوب الذي تم استدعاؤه في الأصل وإلى كافة وسائطه (أسلوب الأسلوب، وسيطات Object[]). بمعنى آخر، إذا قمنا باستدعاء طريقة proxyArnold.introduce(arnold.getName()) بحيث يتم استدعاء طريقة invocation() بدلاً من طريقة الإدخال() ، فداخل هذه الطريقة لدينا إمكانية الوصول إلى طريقة الإدخال الأصلية وحجته! ونتيجة لذلك، يمكننا أن نفعل هذا:
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);
}
}
الآن في طريقة الاستدعاء () أضفنا استدعاء للطريقة الأصلية. إذا حاولنا الآن تشغيل الكود من مثالنا السابق:
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
متى قد تحتاج هذا؟ في الواقع، في كثير من الأحيان. يتم استخدام نمط تصميم "الوكيل الديناميكي" بشكل نشط في التقنيات الشائعة... أوه، بالمناسبة، نسيت أن أذكر أن الوكيل الديناميكي هو نمط تصميم! تهانينا، لقد تعلمت شيئًا آخر! :) على سبيل المثال، يتم استخدامه بنشاط في التقنيات والأطر الشائعة المتعلقة بالأمن. تخيل أن لديك 20 طريقة يجب تنفيذها فقط من قبل المستخدمين الذين قاموا بتسجيل الدخول إلى برنامجك. باستخدام التقنيات التي تعلمتها، يمكنك بسهولة إضافة فحص إلى هذه الطرق العشرين لمعرفة ما إذا كان المستخدم قد أدخل بيانات اعتماد صالحة دون تكرار رمز التحقق في كل طريقة. أو لنفترض أنك تريد إنشاء سجل حيث سيتم تسجيل جميع إجراءات المستخدم. من السهل أيضًا القيام بذلك باستخدام الوكيل. حتى الآن، يمكنك ببساطة إضافة تعليمات برمجية إلى مثالنا أعلاه بحيث يتم عرض اسم الطريقة عند الاتصال بـ invocation() ، وهذا من شأنه أن ينتج سجلًا بسيطًا للغاية لبرنامجنا :) في الختام، انتبه إلى أحد القيود المهمة. يعمل كائن الوكيل مع الواجهات وليس الفئات. يتم إنشاء وكيل للواجهة. ألق نظرة على هذا الكود:
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
هنا نقوم بإنشاء وكيل خصيصًا لواجهة الشخص . إذا حاولنا إنشاء وكيل للفئة، أي تغيير نوع المرجع ومحاولة الإرسال إلى فئة Man ، فلن ينجح الأمر.
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
proxyArnold.introduce(arnold.getName());
الاستثناء في مؤشر الترابط "الرئيسي" java.lang.ClassCastException: لا يمكن إرسال com.sun.proxy.$Proxy0 إلى الرجل. يعد وجود واجهة مطلبًا مطلقًا. تعمل الوكلاء مع الواجهات. هذا كل شيء لهذا اليوم :) حسنًا، الآن سيكون من الجيد حل بعض المهام! :) حتى المرة القادمة!
GO TO FULL VERSION