شرح مفصل للوكيل الديناميكي JDK
تقدم هذه المقالة بشكل أساسي المبادئ الأساسية للوكالة الديناميكية JDK ، بحيث يمكن للجميع فهم وكيل JDK بشكل أعمق ، معرفة ما هو عليه. فهم المبدأ الحقيقي للوكيل الديناميكي JDK وعملية توليده. في المستقبل ، عندما نكتب وكيل JDK ، يمكننا كتابة وكيل مثالي دون التحقق من العرض التوضيحي. دعنا أولاً نأخذ عرضًا بسيطًا. تعتمد عملية التحليل اللاحقة على هذا العرض التوضيحي لتقديمها. يتم تشغيل المثال باستخدام JDK 1.8.
JDK Proxy HelloWorld
package com.yao.proxy ؛/** * تم إنشاؤه بواسطة robin */public interface helloWorld {void sayhello () ؛} package com.yao.proxy ؛ import com.yao.helloworld ؛/** * تم إنشاؤه بواسطة Robin */public class HelloWorLdImpl تنفذ helloworld {public void sayhello () {system.out.print ("Hello World") ؛ }} package com.yao.proxy ؛ import java.lang.reflect.invocationHandler ؛ import java.lang.reflect.method ؛/** * تم إنشاؤه بواسطة robin */public class myinvocationhandler invocationhandler {target compue private ؛ public myinvocationHandler (الهدف الكائن) {this.target = target ؛ } استدعاء الكائن العام (وكيل الكائن ، الطريقة ، الكائن [] args) يلقي رمي {system.out.println ("الطريقة:"+ method.getName ()+ "تم استدعاء!") ؛ طريقة العودة. invoke (الهدف ، args) ؛ }} package com.yao.proxy ؛ استيراد com.yao.helloworld ؛ استيراد java.lang.reflect.constructor ؛ استيراد java.lang.reflect.invocationHandler {public static void main (string [] args) يلقي nosuchmethodexception ، غير aluctionalAccessException ، invocationTargetException ، instantiationException {// هناك طريقتان للكتابة هنا. نستخدم طريقة معقدة قليلاً للكتابة ، والتي ستساعد الجميع على فهم المزيد. class <؟> proxyclass = proxy.getproxyclass (jdkproxytest.class.getClassloader () ، helloworld.class) ؛ المُنشئ النهائي <؟> cons = proxyclass.getConstructor (invocationHandler.class) ؛ invocationHandler النهائي ih = جديد myinvocationHandler (new HelloWorldImpl ()) ؛ HelloWorld HelloWorld = (HelloWorld) cons.newinstance (IH) ؛ HelloWorld.Sayhello () ؛ // فيما يلي طريقة أبسط للكتابة ، وذلك بشكل أساسي كما هو موضح أعلاه/* HelloWorld HelloWorld = (HelloWorld). NewProxyInstance (jdkproxytest.class.getClassloader () ، فئة جديدة <؟> [] {helloworld.class} ، myinvocationHandler new (new HelloWorLdImpl ())) ؛ HelloWorld.Sayhello () ؛ */}}قم بتشغيل الرمز أعلاه وسيتم تنفيذ وكيل JDK البسيط.
عملية توليد الوكيل
السبب في أننا نسمي JDK Dynamic Proxy كل يوم هو أن فئة الوكيل هذه يتم إنشاؤها ديناميكيًا بواسطة JDK بالنسبة لنا في وقت التشغيل. قبل شرح عملية توليد الوكيل ، نضيف أولاً المعلمة -dsun.misc.proxygenerator.SavegeneryatedFiles = True to JVM Startup. تتمثل وظيفتها في مساعدتنا في حفظ رمز Bytecode لفئة الوكيل التي تم إنشاؤها ديناميكيًا بواسطة JDK إلى القرص الثابت ، ومساعدتنا على عرض المحتوى المحدد للوكيل الذي تم إنشاؤه. لقد استخدمت Intellij Idea ، وبعد إنشاء فئة الوكيل ، تم وضعها مباشرة في الدليل الجذر للمشروع ، مع اسم الحزمة المحدد كهيكل دليل.
تتضمن عملية توليد فئة وكيل أساسًا جزأين:
إدخال طريقة getProxyclass لفئة الوكيل: تحتاج إلى تمرير في محمل الفئة والواجهة
ثم استدعاء طريقة getProxyclass0 ، والشرح في ذلك واضح للغاية. إذا كانت فئة الوكيل التي تنفذ الواجهة الحالية موجودة ، فسيتم إرجاعها مباشرة من ذاكرة التخزين المؤقت. إذا لم يكن موجودًا ، فسيتم إنشاؤه من خلال proxyclassfactory. يمكن أن نرى هنا بوضوح أن هناك حد لعدد واجهات الواجهة ، والتي لا يمكن أن تتجاوز 65535. معلومات التهيئة المحددة لـ proxyclasscache هي كما يلي:
proxyclasscache = new DefenCache <> (جديد keyfactory () ، proxyclassfactory ()) ؛
يتم إنشاء المنطق المحدد لإنشاء فئة وكيل من خلال طريقة تطبيق proxyclassfactory.
يتضمن المنطق في ProxyClassFactory منطق إنشاء اسم الحزمة ، واتصال ProxyGenerator. ClotateProxyclass لإنشاء فئة الوكيل ، وتحميل Bytecode فئة الوكيل في JVM.
1. منطق توليد اسم الحزمة الافتراضي هو com.sun.proxy. إذا كانت فئة الوكيل هي واجهة الوكيل غير العامة ، فسيتم استخدام نفس اسم الحزمة مثل واجهة فئة الوكيل. اسم الفئة الافتراضية هو $ proxy بالإضافة إلى قيمة عدد صحيح مملوءة ذاتيا.
2. بعد أن يكون اسم الحزمة جاهزًا ، يتم إنشاء رمز Bytecode الوكيل وفقًا للواجهة الواردة المحددة من خلال ProxyGenerator. generateproxyclass. المعلمة -dsun.misc.proxygenerator.SavegeneredFiles = True يلعب دورًا في هذه الطريقة. إذا كان هذا صحيحًا ، احفظ رمز Bytecode إلى القرص. في فئة الوكيل ، فإن جميع أساليب الوكيل هي نفسها لاستدعاء طريقة Invoke لـ InvocationHander. يمكننا أن نرى نتائج فك الوكيل المحددة في وقت لاحق.
3. قم بتحميل رمز bytecode في JVM من خلال تحميل الفئة المرتفع: exenteclass0 (loader ، proxyname ، proxyclassfile ، 0 ، proxyclassfile.length) ؛.
Private Static Class ProxyclassFactory Parmements bifunction <classloader ، class <؟> [] ، class <؟ >> {// prefix لجميع أسماء فئة الوكيل الخاصة بالسلسلة النهائية الثابتة proxyclassnameprefix = "$ proxy" ؛ // الرقم التالي الذي يجب استخدامه لتوليد أسماء فئة وكيل فريدة من نوعها في Atomiclong NextUniquenber = new Atomiclong () ؛ @Override Public Class <؟> apper (classloader loader ، class <؟> [] interfaces) {map <class <؟> ، boolean> interfaceset = new IdentityHashMap <> (interfaces.length) ؛ لـ (class <؟> intf: interfaces) { / * * حدد أن تحميل الفئة يحل اسم الواجهة * إلى نفس كائن الفئة. */ class <؟> interfaceClass = null ؛ حاول {interfaceClass = class.forname (intf.getName () ، false ، loader) ؛ } catch (classnotfoundException e) {} if (interfaceClass! = intf) {throw new alficalArgumentException (intf + "غير مرئي من loader class") ؛ } / * * تحقق من أن كائن الفئة يمثل بالفعل واجهة *. */ if (! interfaceClass.isInterface ()) {رمي new alficalArgumentException (interfaceClass.getName () + "ليست واجهة") ؛ } / * * تحقق من أن هذه الواجهة ليست مكررة. */ if (interfaceset.put (interfaceClass ، boolean.true)! = null) {رمي new alfictalargumentException ("الواجهة المتكررة:" + interfaceClass.getName ()) ؛ }} سلسلة proxypkg = null ؛ // حزمة لتحديد فئة الوكيل في int accessflags = modifier.public | modifier.final ؛ / * * سجل حزمة واجهة الوكيل غير الحكومية بحيث يتم تعريف فئة * الوكيل في نفس الحزمة. تحقق من أن * جميع واجهات الوكيل غير العامة موجودة في نفس الحزمة. */// إنشاء اسم الحزمة ونقص اسم الفئة لـ (الفئة <؟> intf: interfaces) {int flags = intf.getModiFiers () ؛ if (! modifier.ispublic (flags)) {accessflags = modifier.final ؛ اسم السلسلة = intf.getName () ؛ int n = name.lastindexof ('.') ؛ String pkg = ((n == -1)؟ "": name.subString (0 ، n + 1)) ؛ if (proxypkg == null) {proxypkg = pkg ؛ } if if (! pkg.equals (proxypkg)) {رمي جديد alficalArgumentException ("واجهات غير عامة من حزم مختلفة") ؛ }}}} if (proxypkg == null) {// إذا لم يكن هناك واجهات الوكيل غير الحكومية ، استخدم proxypkg proxypkg com.sun.proxy com.sun.proxy proxypkg = respectutil.proxy_package + "." ؛ } / * * اختر اسمًا لفئة الوكيل لإنشاء. */ long num = nextUniquenumber.getandincrement () ؛ سلسلة proxyname = proxypkg + proxyclassnamePrefix + num ؛ / * * إنشاء فئة الوكيل المحددة. قم بإنشاء رمز bytecode من فئة الوكيل* -dsun.misc.proxygenerator.savegeneratedfiles = صحيح للعمل في هذا القسم*/ byte [] proxyclassfile = proxygenerator.generateproxyclass (proxyname ، interfaces ، accesslags) ؛ جرب {// تحميل في jvm return defineclass0 (loader ، proxyname ، proxyclassfile ، 0 ، proxyclassfile.length) ؛ } catch (classFormaterror e) { / * * A classformaterror هنا يعني (منع الحشرات في رمز توليد فئة البروكسي *) كان هناك بعض الجوانب غير الصالحة للوسائط المقدمة لإنشاء فئة الوكيل (مثل قيود الجهاز الظاهري * تجاوزت). */ رمي جديد غير unalfalArgumentException (E.ToString ()) ؛ }}}}يمكننا إلغاء تجميعنا بناءً على رمز Bytecode لفئة الوكيل ، ويمكننا الحصول على النتائج التالية. HelloWorld يحتوي فقط على طريقة Sayhello ، ولكن هناك أربع طرق في فئة الوكيل تتضمن ثلاث طرق على الكائن: متساوي ، tostring ، و hashcode.
يتضمن الهيكل الخشن للوكيل 4 أجزاء:
package com.sun.proxy ؛ import com.yao.helloworld ؛ import java.lang.reflect.invocationHandler ؛ import java.lang.reflect.method ؛ import java.lang.reflect.proxy ؛ import java.lang.reflect.undecledthrowabletible ؛ طريقة ثابتة M1 ؛ الطريقة الثابتة الخاصة M3 ؛ الطريقة الثابتة الخاصة M2 ؛ الطريقة الثابتة الخاصة M0 ؛ public $ proxy0 (invocationHandler var1) رمي {super (var1) ؛ } النهائي العام يساوي (كائن var1) يلقي {try {return ((boolean) super.h.invoke (this ، m1 ، كائن جديد [] {var1})). booleanvalue () ؛ } catch (runTimeException | خطأ var3) {throw var3 ؛ } catch (throwable var4) {رمي جديد غير محدد غير قابل للمعاناة (var4) ؛ }} public final void sealhhello () رمي {try {super.h.invoke (this ، m3 ، (object []) null) ؛ } catch (runTimeException | خطأ var2) {throw var2 ؛ } catch (throwse var3) {رمي جديد غير محدد غير قابل للاعتماد (var3) ؛ }} السلسلة النهائية العامة toString () يلقي {try {return (string) super.h.invoke (this ، m2 ، (object []) null) ؛ } catch (runTimeException | خطأ var2) {throw var2 ؛ } catch (throwse var3) {رمي جديد غير محدد غير قابل للاعتماد (var3) ؛ }} public final int hashcode () رمي {try {return ((integer) super.h.invoke (this ، m0 ، (object [])). Intvalue () ؛ } catch (runTimeException | خطأ var2) {throw var2 ؛ } catch (throwse var3) {رمي جديد غير محدد غير قابل للاعتماد (var3) ؛ }} static {try {m1 = class.forname ("java.lang.object"). getMethod ("equals" ، class [] {class.forname ("java.lang.object")}) ؛ m3 = class.forname ("com.yao.helloworld"). getMethod ("sayhello" ، فئة جديدة [0]) ؛ m2 = class.forname ("java.lang.object"). getMethod ("toString" ، فئة جديدة [0]) ؛ m0 = class.forname ("java.lang.object"). getMethod ("hashcode" ، فئة جديدة [0]) ؛ } catch (nosuchmethodexception var2) {رمي nosuchmethoderror جديد (var2.getMessage ()) ؛ } catch (classnotfoundException var3) {رمي noclassDeffounderror (var3.getMessage ()) ؛ }}}التعليمات:
1. TOSTRING () HashCode () Quale () Method Method Logic: إذا تم استدعاء الطرق على هذه الكائنات الثلاثة ، فسيتم تمر عبر منطق InvocationHandler مثل طرق وطرق الواجهة الأخرى. يمكن رؤيتها بوضوح من نتائج Bytecode أعلاه. لن تتبع الطرق الأخرى على الكائنات منطق معالجة الوكيل ، ولكنها ستتبع مباشرة منطق الطريقة على الكائن الموروثة بالوكالة.
2. عندما تحتوي الواجهة على أساليب متساوية وطرق تراكب ترنغ ، ستتبع منطق معالج الاحتجاج تمامًا مثل التعامل مع طرق الواجهة العادية ، ويؤدي إلى تشغيل منطق الطريقة بناءً على إعادة كتابة الكائن الهدف ؛
3. تحتوي الواجهة على توقيعات طريقة مكررة ، والتي تخضع للترتيب الذي يتم فيه تمرير الواجهة. كل من يستخدم الطريقة التي أمامه سيحتفظ فقط بأسلوب واحد في فئة الوكيل ، ولن يكون هناك توقيعات تكرار ؛
شكرا لك على القراءة ، آمل أن تساعدك. شكرا لك على دعمك لهذا الموقع!