بالصدفة اليوم، أردت فجأة إلقاء نظرة على الوكيل الديناميكي لـ JDK، لأنني كنت أعرف القليل عنه من قبل، وأردت فقط اختبار استخدامه، وسرعان ما كتبت هذه الواجهات والفئات:
فئة الواجهة: UserService.java
انسخ رمز الكود كما يلي:
الحزمة com.yixi.proxy;
واجهة المستخدم العامة {
حفظ كثافة العمليات العامة () ؛
تحديث الفراغ العام (معرف كثافة العمليات)؛
}
فئة التنفيذ: UserServiceImpl.java
انسخ رمز الكود كما يلي:
الحزمة com.yixi.proxy;
فئة عامة UserServiceImpl تنفذ UserService {
@تجاوز
حفظ كثافة العمليات العامة () {
System.out.println("حفظ المستخدم...");
العودة 1؛
}
@تجاوز
تحديث الفراغ العام (معرف كثافة العمليات) {
System.out.println("تحديث مستخدم" + id);
}
}
ثم كتبت بفارغ الصبر InvocationHandler الذي أردته: وظيفته بسيطة للغاية، وهي تسجيل وقت البدء ووقت الانتهاء لتنفيذ الطريقة.
TimeInvocationHandler.java
انسخ رمز الكود كما يلي:
الحزمة com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
الطبقة العامة TimeInvocationHandler تنفذ InvocationHandler {
@تجاوز
استدعاء الكائن العام (وكيل الكائن، طريقة الطريقة، وسيطة الكائن [])
رميات قابلة للرمي {
System.out.println("startTime : " +System.currentTimeMillis());
Object obj =method.invoc(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
كائن الإرجاع؛
}
}
بعد الانتهاء من جميع الاستعدادات، حان الوقت بالطبع لبدء كتابة الاختبارات!
اختبار.java
انسخ رمز الكود كما يلي:
الحزمة com.yixi.proxy;
import java.lang.reflect.Proxy;
اختبار الطبقة العامة {
public static void main(String[] args) { 9 TimeInvocationHandler timeHandler = new TimeInvocationHandler();
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.update(2);
u.save();
}
}
لقد سار الأمر بسعادة، لكنه لم يمنحك أي وجه، وكانت النتيجة استثناءً ملأ الشاشة:
انسخ رمز الكود كما يلي:
وقت البدء: 1352877835040
وقت البدء: 1352877835040
وقت البدء: 1352877835040
استثناء في مؤشر الترابط "الرئيسي" java.lang.reflect.UndeclaredThrowableException
على $Proxy0.update (مصدر غير معروف)
في com.yixi.proxy.Test.main(Test.java:11)
المتسبب عن: java.lang.reflect.InvocationTargetException
في sun.reflect.NativeMethodAccessorImpl.invoc0 (الطريقة الأصلية)
على sun.reflect.NativeMethodAccessorImpl.invoc(NativeMethodAccessorImpl.java:39)
في sun.reflect.DelegatingMethodAccessorImpl.invoc (DelegatingMethodAccessorImpl.java:25)
في java.lang.reflect.Method.invoc(Method.java:597)
في com.yixi.proxy.TimeInvocationHandler.invoc(TimeInvocationHandler.java:12)
... 2 أكثر
الاستثناء com.yixi.proxy.TimeInvocationHandler.invoc(TimeInvocationHandler.java:12) يوضح المشكلة بوضوح في السطر 12 من TimeInvocationHandle: أي
انسخ رمز الكود كما يلي:
استدعاء الكائن العام (وكيل الكائن، طريقة الطريقة، وسيطة الكائن [])
رميات قابلة للرمي {
System.out.println("startTime : " +System.currentTimeMillis());
Object obj =method.invoc(proxy, args);
System.out.println("endTime : " +System.currentTimeMillis());
كائن الإرجاع؛
}
لا يوجد شيء خاطئ في الطريقة! نظرًا لأن طريقة الاستدعاء () تبدو أنها توفر جميع المعلمات المطلوبة بواسطة الأسلوب.invoc(Object, Object[])، فسنستخدمها كأمر طبيعي. إذا كنت تعتقد حقًا بهذه الطريقة، فقد خدعت JDK فخ دعونا نلقي نظرة على الطريقة الصحيحة لكتابتها أولا، في حالة عدم رغبة بعض الطلاب في قراءة ما يلي، على الأقل أعطني الحل الصحيح:
تعديل TimeInvocationHandler.java
انسخ رمز الكود كما يلي:
الحزمة com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
الطبقة العامة TimeInvocationHandler تنفذ InvocationHandler {
كائن خاص س؛
عام TimeInvocationHandler(كائن س){
this.o = o;
}
@تجاوز
استدعاء الكائن العام (وكيل الكائن، طريقة الطريقة، وسيطة الكائن [])
رميات قابلة للرمي {
System.out.println("startTime : " +System.currentTimeMillis());
Object obj =method.invoc(o, args);
System.out.println("endTime : " +System.currentTimeMillis());
كائن الإرجاع؛
}
}
تعديل Test.java
انسخ رمز الكود كما يلي:
الحزمة com.yixi.proxy;
import java.lang.reflect.Proxy;
اختبار الطبقة العامة {
public static void main(String[] args) {
TimeInvocationHandler timeHandler = new TimeInvocationHandler(new UserServiceImpl());
UserService u = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), timeHandler);
u.update(2);
u.save();
}
}
الآن هذا هو الإخراج الصحيح:
انسخ رمز الكود كما يلي:
وقت البدء: 1352879531334
تحديث المستخدم 2
وقت الانتهاء: 1352879531334
وقت البدء: 1352879531334
حفظ المستخدم....
وقت الانتهاء: 1352879531335
إذا كنت تريد كودًا أقل، فيمكنك كتابة فصل دراسي مجهول مباشرةً:
الحزمة com.yixi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
اختبار الطبقة العامة {
public static void main(String[] args) {
Final UserServiceImpl ui = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(
usi.getClass().getClassLoader(),
usi.getClass().getInterfaces(),
معالج الاحتجاج الجديد () {
@تجاوز
استدعاء الكائن العام (وكيل الكائن، طريقة الطريقة، وسيطة الكائن [])
رميات قابلة للرمي {
System.out.println("startTime : " +System.currentTimeMillis());
Object obj =method.invoc(usi, args);
System.out.println("endTime : " +System.currentTimeMillis());
كائن الإرجاع؛
}
});
u.update(2);
u.save();
}
}
نظرًا لأن المعلمة الأولى فيmethod.invocation(target,args); هي الكائن الهدف، فلماذا تحتاج طريقة InvocationHandler إلى معلمة وكيل الكائن؟ دعونا ننظر إلى أسفل!
بالنسبة لطريقة الاستدعاء الأكثر أهمية (في رأيي الشخصي)، فلنلقي نظرة على ما تقوله JDK:
انسخ رمز الكود كما يلي:
استدعاء
استدعاء الكائن (وكيل الكائن،
طريقة الطريقة،
الكائن [] الوسائط)
يلقي استدعاءات أسلوب المقابض القابلة للرمي على مثيل الوكيل ويعيد النتيجة. يتم استدعاء هذه الطريقة على معالج الاستدعاء عندما يتم استدعاء الطريقة على مثيل الوكيل المرتبط بها.
المعلمة:
الوكيل - مثيل الوكيل الذي يتم استدعاء الطريقة عليه
الطريقة - مثيل الطريقة المطابق لطريقة الواجهة التي يتم استدعاؤها في مثيل الوكيل. ستكون فئة الإعلان لكائن الطريقة هي الواجهة التي يتم فيها الإعلان عن الطريقة، والتي قد تكون واجهة فائقة لواجهة الوكيل التي ترث منها فئة الوكيل الطريقة.
args - مصفوفة من الكائنات التي تحتوي على قيم الوسيطات التي تم تمريرها إلى استدعاء الأسلوب على مثيل الوكيل، أو فارغة إذا كانت طريقة الواجهة لا تأخذ أي وسيطات. يتم تغليف معلمات الأنواع الأساسية في مثيلات فئة الغلاف الأساسية المناسبة (مثل java.lang.Integer أو java.lang.Boolean).
الوكيل - مثيل الوكيل الذي يتم استدعاء الطريقة عليه؟ ماذا تعني هذه الجملة؟ التمثيل؟ الطريقة هي طريقة الوكيل؟ إذًا ألا ينبغي أن تكون طريقتي في تنفيذ الوكيل هي Object obj =method.invoc(proxy, args);؟ لم أتحول في ذلك الوقت إلى مجموعة المناقشة وذهبت إلى Google ولكن لم أتمكن من العثور على أي إلهام. اعتقدت أنه من الأفضل أن ألقي نظرة على الكود المصدري وربما أرى شيئًا ما.
افتح الكود المصدري لفئة Proxy واكتشف ماهية المنشئ:
انسخ رمز الكود كما يلي:
protectedInvocationHandler h;
الوكيل المحمي (InvocationHandler h) {
this.h = h;
}
استخدم InvocationHandler كمعلمة لمنشئ Proxy.... إذن ما الذي يستخدم InvocationHandler من أجله؟ هل هناك أي اتصال بطريقة الاستدعاء () في InvocationHandler؟
فكرتي الأولى هي أن Proxy سوف يستدعي العبارة التالية داخليًا:
انسخ رمز الكود كما يلي:
h.invoc(this,methodName,args);
لأنه يتعين عليك استدعاء طريقة الاستدعاء لتنفيذ الطريقة المقابلة.
دعونا نلقي نظرة على هذا أولا
ستجد هنا شيئًا يبدو منطقيًا: عندما u.update(2)، سيستدعي àProxy Handler.invocation(proxyClass,update,2) à، وهو ما يعني proxyClass.update(2);
عندما u.save(); سوف يقوم àProxy باستدعاء Handler.invoc(proxyClass,save,null) أي proxyClass.save();
عندما يتم تغيير Test.java إلى هذا:
انسخ رمز الكود كما يلي:
اختبار الطبقة العامة {
public static void main(String[] args) {
Final UserServiceImpl ui = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(
usi.getClass().getClassLoader(),
usi.getClass().getInterfaces(),
معالج الاحتجاج الجديد () {
@تجاوز
استدعاء الكائن العام (وكيل الكائن، طريقة الطريقة، وسيطة الكائن [])
رميات قابلة للرمي {
عودة فارغة؛
}
});
u.update(2);
u.save();
}
}
لاحظ أن طريقة الفئة المجهولة في هذا الوقت ترجع فارغة إذا قمت بتشغيلها، فستجد:
انسخ رمز الكود كما يلي:
استثناء في مؤشر الترابط "الرئيسي" java.lang.NullPointerException
في $Proxy0.save (مصدر غير معروف)
في com.yixi.proxy.Test.main(Test.java:17)
يوجد مؤشر فارغ في السطر 17، أي أن طريقة u.save() هنا تحتوي على عنصر فارغ. لا ينبغي أن يكون الأمر كذلك. إذا كانت u.update(2) ستبلغ عن استثناء مؤشر فارغ هناك عندما أعلق على السطر 17، يختفي الاستثناء، مما يشير إلى أنه يمكن تنفيذ u.update() بشكل طبيعي. فلماذا هذا؟
في الواقع، هذا هو سبب إرجاع طريقة الاستدعاء فارغة:
انتبه إلى الطريقتين في فئة UserService:
انسخ رمز الكود كما يلي:
واجهة المستخدم العامة {
حفظ كثافة العمليات العامة () ؛
تحديث الفراغ العام (معرف كثافة العمليات)؛
}
تقوم طريقة Save () بإرجاع نوع int وترجع طريقة التحديث نوعًا فارغًا وفقًا للتخمين أعلاه، ويقوم Handler.invocate () بتنفيذ proxyClass.update(2)؛ وطريقة الإرجاع في طريقة الاستدعاء تتوافق مع الإرجاع. قيمة طريقة الوكيل
لذلك عندما تُرجع طريقة الاستدعاء قيمة فارغة، تتلقى طريقة تحديث الوكيل قيمة إرجاع فارغة، وترجع في الأصل فارغة، لذلك لم يتم الإبلاغ عن أي استثناء، ويجب أن يُرجع حفظ الوكيل قيمة من النوع int. ولا يمكن لـ JVM تحويل القيمة الخالية إلى نوع int، لذلك يمكن تفسير هذا الاستثناء بوضوح، ويمكنه أيضًا إثبات التخمين السابق نسبيًا.
يبدو أن وكيل المعلمة الأول في طريقة استدعاء InvocationHandler يهدف فقط إلى السماح لفئة الوكيل بالمرور في مرجع كائن الوكيل proxyClass عند استدعاء الطريقة بمرجع كائن InvocationHandler الخاص به لإكمال الأعمال التي يحتاج proxyClass إلى إكمالها .
الموهبة الأدبية ليست جيدة! القدرة محدودة! أتمنى يا رفاق أن تصححوا لي ...