من خلال تحليل المقالات السابقة ، نعلم أن فئة الوكيل يتم إنشاؤها من خلال مصنع proxyclassfactory لفئة الوكيل. سوف تستدعي فئة المصنع هذه طريقة colareproxyclass () لفئة proxygenerator لإنشاء رمز Bytecode لفئة الوكيل. يتم تخزين فئة Proxygenerator في حزمة Sun.Misc. يمكننا العثور على هذا الفئة من خلال رمز مصدر OpenJDK. المحتوى الأساسي للطريقة الثابتة لـ endrendproxyclass () هو استدعاء طريقة مثيل classclassfile () لإنشاء ملفات فئة. دعنا نلقي نظرة على ما يتم داخل طريقة cenerateClassFile ().
بايت الخاص [] generateClassFile () {// الخطوة الأولى هي تجميع جميع الطرق في كائنات proxymethod // توليد طرق الوكيل أولاً مثل tostring و hashcode و equals ، إلخ. addproxymethod (equalsmethod ، object.class) ؛ addproxymethod (ToStringMethod ، object.class) ؛ // نقل كل طريقة لكل واجهة وإنشاء كائن proxymethod له (int i = 0 ؛ i <interfaces.length ؛ i ++) {method [] methods = interfaces [i] .getMethods () ؛ لـ (int j = 0 ؛ j <methods.length ؛ j ++) {addProxymethod (الطرق [j] ، واجهات [i]) ؛ }} // لطرق الوكيل بنفس التوقيع ، تحقق مما إذا كانت قيمة الإرجاع للطريقة متوافقة مع (قائمة <Proxymethod> التوقيعات: proxymethods.values ()) {checkReturnTypes (sigmethods) ؛ }. // نقل طريقة الوكيل في ذاكرة التخزين المؤقت لـ (قائمة <ProxyMethod> signmethods: proxymethods.values ()) {for (proxymethod pm: signmethods) {// إضافة حقول ثابتة لفئة الوكيل ، على سبيل المثال: طريقة ثابتة m1 ؛ Fields.Add (New FieldInfo (PM.MethodfieldName ، "ljava/lang/respense/method ؛" ، acc_private | acc_static)) ؛ // إضافة طرق وكيل لطريقة فئة الوكيل. }} // إضافة طريقة تهيئة الحقل الثابت. } catch (ioException e) {رمي internalerror جديد ("استثناء غير متوقع I/O") ؛ } // لا يمكن أن تكون طريقة التحقق وجمع الحقل أكبر من 65535 إذا (methods.size ()> 65535) {رمي New IllugalArgumentException ("تم تجاوز حد الطريقة") ؛ } if (fields.size ()> 65535) {رمي new alficalArgumentException ("الحد من الحقل تجاوز") ؛ } if (fields.size ()> 65535) {رمي new alficalArgumentException ("الحد من الحقل تجاوز") ؛ }. // تحقق من وجود اسم مؤهل بالكامل لفئة الوالدين من فئة الوكيل في المجموعة الثابتة ، واسم الفئة الأصل هو: "Java/Lang/Reflect/Proxy" cp.getClass (SuperClassName) ؛ // تحقق من أن الاسم الكامل المؤهل لواجهة فئة الوكيل لـ (int i = 0 ؛ i <interfaces.length ؛ i ++) {cp.getClass (dottoslash (interfaces [i] .getName ())) ؛ } // بجانب البدء في كتابة الملف ، قم بتعيين تجمع ثابت لقراءة CP.SetReadOnly () فقط ؛ bytearrayoutputstream bout = new bytearrayoutputStream () ؛ DataOutputStream Dout = جديد DataOutputStream (bout) ؛ حاول {// 1. اكتب إلى الرقم السحري dout.writeint (0xcafebabe) ؛ // 2. اكتب إلى رقم الإصدار الثانوي dout.writeshort (classfile_minor_version) ؛ // 3. اكتب إلى رقم الإصدار الرئيسي dout.writeshort (classfile_major_version) ؛ // 4. اكتب إلى تجمع ثابت cp.write (Dout) ؛ // 5. اكتب Access Modifier Dout.writeShort (ACC_Public | acc_final | acc_super) ؛ // 6. اكتب فئة فئة dout.writeshort (cp.getClass (dottoslash (className))) ؛ // 7. اكتب فهرس الفئة الأصل ، يتم توريث فئات الوكيل التي تم إنشاؤها من الوكيل dout.writeshort (cp.getClass (superclassname)) ؛ // 8. اكتب قيمة عدد الواجهة dout.writeshort (interfaces.length) ؛ // 9. كتابة واجهة تعيين (int i = 0 ؛ i <interfaces.length ؛ i ++) {dout.writeshort (cp.getClass (dottoslash (interfaces [i] .getName ()))) ؛ } // 10. اكتب قيمة عدد الحقل dout.writeshort (fields.size ()) ؛ // 11. اكتب مجموعة الحقل لـ (FieldInfo F: Fields) {f.write (dout) ؛ } // 12. كتابة طريقة عدد القيمة dout.writeshort (methods.size ()) ؛ // 13. مجموعة طريقة الكتابة لـ (MethodInfo M: Methods) {M.Write (dout) ؛ } // 14. اكتب قيمة حساب الخصائص ، لا يحتوي ملف فئة الوكيل على سمات ، لذلك فهو 0 dout.writeshort (0) ؛ } catch (ioException e) {رمي internalerror جديد ("استثناء غير متوقع I/O") ؛ } // تحويل إلى صفيف ثنائي إلى إخراج الإرجاع bout.tobytearray () ؛}يمكنك أن ترى أن طريقة generateClassFile () يتم تقطيعها ديناميكيًا وفقًا لهيكل ملف الفئة. ما هو ملف الفصل؟ هنا سنشرح أولاً أن ملف Java الذي نكتبه عادةً ما يكتبه. java. بعد كتابتها ، قم بتجميعها من خلال المترجم وإنشاء ملف .class. ملف .class هو ملف فئة. يعتمد تنفيذ برامج Java فقط على ملفات الفصل وليس له أي علاقة بملفات Java. يصف ملف الفئة هذا معلومات الفصل. عندما نحتاج إلى استخدام فئة ، سيقوم جهاز Java Virtual بتحميل ملف الفئة لهذه الفئة مقدمًا وإجراء التهيئة والتحقق ذي الصلة. يمكن لجهاز Java Virtual التأكد من إكمال هذه المهام قبل استخدام هذه الفئة. نحتاج فقط إلى استخدامه مع راحة البال ، دون الاهتمام بكيفية تحميلها الجهاز الظاهري Java. بالطبع ، لا يتعين بالضرورة تجميع ملفات الفصل من خلال تجميع ملفات Java. يمكنك حتى كتابة ملفات الفصل مباشرة من خلال محرر نصوص. هنا ، يقوم الوكيل الديناميكي JDK بإنشاء ملفات الفصل بشكل ديناميكي من خلال البرامج. دعنا نعود إلى الكود أعلاه مرة أخرى ونرى أن إنشاء ملف الفصل ينقسم بشكل رئيسي إلى ثلاث خطوات:
الخطوة 1: جمع جميع طرق الوكيل المراد إنشاؤها ، لفها في كائنات proxymethod وتسجيلها في مجموعة الخريطة.
الخطوة 2: جمع جميع معلومات الحقل ومعلومات الطريقة المراد إنشاؤها لملف الفصل.
الخطوة 3: بعد الانتهاء من العمل أعلاه ، ابدأ في تجميع ملف الفصل.
نحن نعلم أن الجزء الأساسي من الفصل هو حقوله وطرقه. دعونا نركز على الخطوة الثانية لمعرفة الحقول والأساليب التي تنشئها لفئة الوكيل. في الخطوة الثانية ، تم إجراء الأشياء الأربعة التالية بالترتيب.
1. قم بإنشاء مُنشئ للمعلمة لفئة الوكيل ، وتمرير في مرجع إلى مثيل InvocationHandler واستدعاء مُنشئ المعلمة لفئة الأصل.
2. التكرار عبر مجموعة خريطة أساليب الوكيل ، قم بإنشاء مجال ثابت للطريقة المقابلة لكل طريقة وكيل ، وأضفه إلى مجموعة Fields.
3. التكرار عبر مجموعة خريطة أساليب الوكيل ، قم بإنشاء كائن MethodInfo المقابل لكل طريقة وكيل ، وأضفه إلى مجموعة الطرق.
4. إنشاء طريقة تهيئة ثابتة لفئة الوكيل. تعين طريقة التهيئة الثابتة هذه بشكل أساسي مرجع كل طريقة وكيل إلى الحقل الثابت المقابل.
من خلال التحليل أعلاه ، يمكننا أن نعرف تقريبًا أن الوكيل الديناميكي JDK سيقوم في النهاية بإنشاء فئة وكيل مع البنية التالية بالنسبة لنا:
Public Class Proxy0 يمتد Proxy Passy userdao {// الخطوة 1 ، إنشاء Proxy0 المحمي (InvocationHandler H) {Super (H) ؛ } // الخطوة 2 ، قم بإنشاء طريقة ثابتة في المجال الثابت M1 ؛ // طريقة HashCode طريقة ثابتة خاصة M2 ؛ // يساوي الطريقة الطريقة الثابتة الخاصة M3 ؛ // tostring طريقة ساكنة خاصة m4 ؛ // ... // الخطوة 3 ، إنشاء طريقة proxy override public int hashcode () {try {return (int) h.invoke (this ، m1 ، null) ؛ } catch (throwable e) {refl new undeclaredThrowableException (e) ؛ }} Override public boolean equals (Object obj) {try {object [] args = new Object [] {obj} ؛ العودة (Boolean) H.invoke (هذا ، M2 ، args) ؛ } catch (throwable e) {refl new undeclaredThrowableException (e) ؛ }} Override public string toString () {try {return (string) h.invoke (this ، m3 ، null) ؛ } catch (throwable e) {refl new undeclaredThrowableException (e) ؛ }} Override public void save (user user) {try {// إنشاء صفيف المعلمة ، إذا تمت إضافة معلمات متعددة لاحقًا ، فقط Object [] args = new Object [] {user} ؛ H.invoke (هذا ، M4 ، args) ؛ } catch (throwable e) {refl new undeclaredThrowableException (e) ؛ }} // الخطوة 4 ، قم بإنشاء طريقة التهيئة الثابتة ثابتة {try {class c1 = class.forname (object.class.getName ()) ؛ الفئة C2 = class.forname (userDao.class.getName ()) ؛ m1 = c1.getMethod ("hashcode" ، null) ؛ m2 = c1.getMethod ("equals" ، فئة جديدة [] {object.class}) ؛ m3 = c1.getMethod ("tostring" ، null) ؛ M4 = C2.GetMethod ("Save" ، فئة جديدة [] {user.class}) ؛ // ...} catch (استثناء e) {E.PrintStackTrace () ؛ }}}في هذه المرحلة ، بعد التحليل الطبقات والاستكشاف المتعمق لرمز مصدر JDK ، استعادنا المظهر الأصلي لفئة الوكيل التي تم إنشاؤها ديناميكيًا ، وتم شرح بعض الأسئلة السابقة أيضًا.
1. يرث فئة الوكيل فئة Porxy بشكل افتراضي. نظرًا لأن Java يدعم الوراثة الفردية فقط ، يمكن لـ JDK Dynamic Proxy تنفيذ واجهات فقط.
2. سوف تسمي أساليب الوكيل طريقة Invoke () لـ InvocationHandler ، لذلك نحتاج إلى إعادة كتابة طريقة Invoke () لـ InvocationHandler.
3. عند استدعاء طريقة Invoke () ، يتم تمرير مثيل الوكيل نفسه ، طريقة الهدف ومعلمات طريقة الهدف. وضح كيف تأتي معلمات طريقة الاستدعاء ().
استخدم Proxy0 الذي تم إنشاؤه حديثًا كصف وكيل للاختبار مرة أخرى ، ويمكنك أن ترى أن النتيجة النهائية هي نفس فئة الوكيل التي تم إنشاؤها ديناميكيًا باستخدام JDK. مرة أخرى ، تحليلنا موثوق ودقيق. في هذه المرحلة ، تم الإعلان عن مقالات Series JDK Dynamic Proxy Series. من خلال تحليل هذه السلسلة ، قام المؤلف بحل الشكوك الطويلة في قلبه ، وأعتقد أن فهم القراء للوكالة الديناميكية JDK قد أصبح خطوة واحدة إلى الأمام. ومع ذلك ، فإن المعرفة على الورق دائما ضحلة. إذا كنت ترغب في إتقان تقنية الوكيل الديناميكي JDK بشكل أفضل ، فيمكن للقراء الرجوع إلى هذه السلسلة من المقالات للتحقق من رمز مصدر JDK بمفردهم ، أو تبادل تجربة التعلم مع المؤلف ، ويشير إلى التحليل غير المناسب للمؤلف ، والتعلم معًا ، وإحراز تقدم معًا.
ما سبق هو كل محتوى هذه المقالة. آمل أن يكون ذلك مفيدًا لتعلم الجميع وآمل أن يدعم الجميع wulin.com أكثر.