عملية تحميل الفصل
تتمثل المهمة الرئيسية لعملية تحميل الفصل في تحميل ملفات الفئة في JVM. كما هو موضح في الشكل أدناه ، تنقسم العملية إلى ثلاث خطوات:
1. التحميل: حدد موقع ملف الفئة المراد تحميله وتحميل دفق البايت الخاص به في JVM ؛
2. الارتباط: قم بتخصيص بنية الذاكرة الأساسية للتحميل لحفظ معلوماتها ، مثل الخصائص والأساليب والفئات المرجعية. في هذه المرحلة ، لا يزال الفصل غير متوفر ؛
(1) التحقق: تحقق من دفق البايت المحمّل ، مثل التنسيق والأمان ؛
(2) تخصيص الذاكرة: قم بإعداد مساحة الذاكرة لهذه الفئة لتمثيل خصائصها وطرقها والفئات المرجعية ؛
(3) التحليل: تحميل فئات أخرى تشير إليها هذه الفئة ، مثل فئة الوالدين ، واجهات تنفيذها ، إلخ.
3. التهيئة: تعيين القيم لمتغيرات الفئة.
مستوى تحميل الفئة
الخط المنقط في الشكل أدناه هو عدة لوادر فئة مهمة توفرها JDK ، والوصف التفصيلي هو كما يلي:
(1) Loader فئة Bootstrap: عند بدء فئة تحتوي على الوظيفة الرئيسية ، قم بتحميل حزمة JAR في دليل Java_home/lib أو -xbootclasspath المحدد ؛
(2) Loader فئة التمديد: قم بتحميل حزمة JAR في دليل JAVA_HOME/LIB/EXT أو -djava.ext.dirs الدليل المحدد.
(3) محمل فئة النظام: تحميل classpath أو -djava.class.path Class أو Jar Package في الدليل المحدد.
ما تحتاج إلى معرفته هو:
1. بالإضافة إلى loader فئة bootstrap ، فإن لوادر فئة أخرى هي فئات فرعية من فئة java.lang.classloader ؛
2. لا يتم تنفيذ محمل فئة bootstrap في Java. إذا لم تستخدم محمل فئة مخصصة ، فإن java.lang.string.class.getClassloader () لاغية ، و leder الوالد من Loader فئة الامتداد خالية أيضًا ؛
3. عدة طرق للحصول على لوادر فئة:
(1) الحصول على محمل فئة bootstrap: يجب أن يكون ما تحصل عليه فارغًا عند محاولة الحصول على محمل فئة bootstrap. يمكنك التحقق من ذلك بالطريقة التالية: استخدم طريقة getClassloader لكائنات الفئة في حزمة RT.JAR ، مثل java.lang.string.class.getClassloader () للحصول على أو الحصول على محمل فئة التمديد ، ثم اتصل بالطريقة getParent للحصول عليها ؛
(2) الحصول على محمل فئة التمديد: استخدم طريقة getClassloader لكائن الفئة في حزمة JAR تحت دليل Java_home/lib/ext أو أولاً الحصول على محمل فئة النظام ، ثم الحصول عليه من خلال طريقة GetParent ؛
(3) الحصول على محمل فئة النظام: اتصل بالطريقة getClassloader لكائن الفئة الذي يحتوي على الوظيفة الرئيسية ، أو call thread.currentThRead (). getContextClassLoader () أو call classloader.getSystemClasslassloader () ضمن الوظيفة الرئيسية ؛
(4) الحصول على محمل الفئة المعرفة من قبل المستخدم: اتصل بالطريقة getClassloader لكائن الفئة أو call refloy.currentThread (). getContextClassLoader () ؛
المبادئ التشغيلية للطبقة المحمولة
1. مبدأ الوكيل
2. مبدأ الرؤية
3. مبدأ التفرد
4. مبدأ الوكيل يشير مبدأ الوكيل إلى محمل فئة يطلب من وكيل المحمل الأصل للتحميل عند تحميل فئة ، وسيطلب المحمل الأصل أيضًا وكيل المحمل الأصل لتحميله ، كما هو موضح في الشكل أدناه.
لماذا تستخدم وضع الوكيل؟ بادئ ذي بدء ، هذا يمكن أن يقلل من تحميل مكرر للفئة. (هل هناك أي سبب آخر؟)
أين يساء فهمه:
بشكل عام ، يُعتقد أن ترتيب الوكيل الخاص بدروس الفصل هو الوالد أولاً ، أي:
1. عند تحميل فئة ، يتحقق Loader من الفئة أولاً مما إذا كان قد قام بتحميل الفصل. إذا تم تحميله ، فسوف يعود ؛ خلاف ذلك ، اطلب من الوالدين أن يتحولوا إلى الوكيل ؛
2. يكرر المحمل الأصل تشغيل 1 حتى محمل فئة bootstrap ؛
3. إذا لم يتم تحميل محمل فئة Bootstrap ، فسوف يحاول تحميله ، وإذا نجح ، فسيعود ؛ إذا فشلت ، يتم طرح classnotfoundException ، سيتم تحميله بواسطة اللودر الطفل ؛
4. اللودر الفرعي يمسك الاستثناء ويحاول تحميله. إذا نجحت ، فإنه يعود. إذا فشلت ، فإنه يلقي classnotfoundException حتى يتم بدء تشغيل محمل الفئة الفرعية المحملة.
هذا الفهم صحيح بالنسبة للوادر مثل Loader فئة Bootstrap ، وعملية فئة التمديد ، وعملية فئة النظام ، ولكن بعض اللوادر المخصصة ليست هي الحالة. على سبيل المثال ، تعد بعض اللوادر التي يتم تنفيذها بواسطة IBM Web Sphere Server هي الوالدين الأخير ، وهو اللودر الطفل الذي يحاول تحميله أولاً. إذا فشل الحمل ، فسيتم طلب المحمل الأصل. والسبب في ذلك هو: إذا كنت تتوقع استخدام إصدار معين من Log4J من قبل جميع التطبيقات ، فضعه في مكتبة Was_home ، وسيتم تحميله عند بدء تشغيله. إذا أراد تطبيق ما استخدام إصدار آخر من Log4J ، فلا يمكن تحقيقه إذا كان يستخدم الأصل أولاً لأن الفئة الموجودة في Log4J تم تحميلها بالفعل في المحمل الأصل. ومع ذلك ، إذا تم استخدام Parent Last ، فإن Loader Class المسؤول عن تحميل التطبيق سيعطي الأولوية لتحميل إصدار آخر من Log4J.
مبدأ الرؤية
تختلف رؤية كل فئة إلى تحميل الفئة ، كما هو موضح في الشكل أدناه.
لتمديد المعرفة ، يستفيد OSGI من هذه الميزة. يتم تحميل كل حزمة بواسطة تحميل فئة منفصل ، بحيث يمكن لكل محمل فئة تحميل إصدار من فئة معينة ، بحيث يمكن للنظام بأكمله استخدام إصدارات متعددة من فئة.
مبدأ التفرد
يتم تحميل كل فئة على الأكثر مرة واحدة في اللودر.
المعرفة الموسعة 1: أن تكون دقيقة ، يشير نمط Singleton إلى كائن Singleton أن نسخة واحدة فقط من فئة في مجموعة من اللوادر الفئة.
المعرفة الموسعة 2: يمكن تحميل فئة بواسطة لوادر فئة متعددة. كل كائن فئة في مساحة الاسم الخاصة به. عند مقارنة كائن الفئة أو النوع الذي يحول المثيل ، ستقارن مساحة الاسم في نفس الوقت ، مثل:
يتم تحميل فئة KLASS بواسطة ClassLoadera ، على افتراض أن كائن الفئة هو Klassa ؛ يتم تحميله بواسطة ClassLoaderB ، على افتراض أن كائن الفئة هو klassb ، ثم Klassa لا يساوي KlassB. في الوقت نفسه ، عندما يتم إلقاء مثيل Classa على Klassb ، سيتم طرح استثناء ClassCastexception.
لماذا تحتاج إلى محمل فئة مخصصة؟ يضيف Loader الفئة المخصصة الكثير من المرونة إلى لغة Java. الاستخدامات الرئيسية هي:
1. يمكن تحميل الفئات من أماكن متعددة ، مثل على الشبكة ، في قاعدة البيانات ، أو حتى ملفات المصدر التي تم تجميعها على الفور ؛
2. بعد التخصيص ، يمكن لوادر الفئة تحميل إصدار معين من ملف الفصل من حيث المبدأ في وقت التشغيل ؛
3. بعد التخصيص ، يمكن لوادر فئة تفريغ بعض الفئات ديناميكيًا ؛
4. بعد التخصيص ، يمكن لوكيل الفئة فك تشفير الفصل وفك ضغطه قبل تحميل الفصل.
تحميل ضمني وصريح للفئات
التحميل الضمني: عند الرجوع إلى فئة أو ورثها أو إنشاء مثيل لها ، سيتم تحميله ضمنيًا. إذا فشل الحمل ، يتم إلقاء noclassDeffounderror.
التحميل الصريح: استخدم الطريقة التالية ، إذا فشل الحمل ، فسيتم طرح ClassNotFoundException.
cl.loadClass () ، CL هو مثيل لوادر الفئة ؛
class.forname () ، يتم تحميلها باستخدام محمل الفئة للفئة الحالية.
إذا كان تنفيذ الكتلة الثابتة للفئة لديه الفئات التالية:
حزمة cn.fengd ؛ الطبقة العامة dummy {static {system.out.println ("hi") ؛ }} إنشاء فئة اختبار أخرى:
حزمة cn.fengd ؛ الطبقة العامة classloadertest {public static void main (string [] args) يلقي instantiationException ، استثناء {try { / * * طرق مختلفة للتحميل. */ class c = classLoaderTest.class.getClassLoader (). loadClass ("cn.fengd.dummy") ؛ } catch (استثناء e) {// todo catch e.printstacktrace () ؛ }}}ما مدى فعالية الجري؟
ماذا لو تم استبداله بـ class.forname ("cn.fengd.dummy") ؛ أو New Dummy ()؟
مرحبا سوف يكون الإخراج.
الأسئلة المتداولة:
1. هل النوع المحدد محملاً بواسطة لوادر فئة مختلفة لا يزال بنفس النوع؟
في Java ، يستخدم الفصل اسم فئة مؤهل بالكامل كمعرف لها. يتضمن اسم فئة المطابقة الدقيق المشار إليه هنا اسم الحزمة واسم الفصل. ومع ذلك ، في JVM ، يستخدم الفصل اسمه الكامل ومثيل من فئة التحميل كمعرفات فريدة. سيتم وضع الفئات التي تم تحميلها بواسطة لوادر فئة مختلفة في مساحات أسماء مختلفة. يمكننا استخدام محمولين من الفئة المخصصة لتحميل نوع مخصص (لاحظ أنه لا يضع رمز Bytecode للنوع المخصص في مسار النظام أو مسار التمديد ، وإلا فإنه سيتم تحميله أولاً بواسطة محمل فئة النظام أو محمل فئة الامتداد) ، ثم استخدام حالتين فئة تم الحصول عليها لجعل java.lang.object.oquals (...) الأحكام ، وستحصل على نتائج غير محددة. يمكن القيام بذلك عن طريق كتابة محمولين من الدرجة المخصصة لتحميل نفس النوع المخصص ثم إصدار حكم ؛ في الوقت نفسه ، يمكنك اختبار تحميل Java.* type ، ثم مقارنة واختبار نتائج الاختبار.
2. اتصل مباشرة بطريقة class.forname (اسم السلسلة) في الكود. ما هو التحميل الفئة الذي سيؤدي إلى تشغيل سلوك تحميل الفصل؟
سيستخدم class.forname (اسم السلسلة) محمل الفئة الذي يتصل بالفئة لتحميل الفئة بشكل افتراضي. دعنا نحلل رمز JDK المقابل مباشرة:
//java.lang.class.java publicstatic class <؟> forname (سلسلة classname) رمي classnotfoundException {return forname0 (className ، true ، classloader.getCallerClasslassloader () getCallerClassLoader () {// احصل على نوع فئة الاتصال (المتصل) Caller = Reflection.getCallerClass (3) ؛ // يمكن أن يكون هذا فارغًا إذا كان VM يطلب ذلك إذا (caller == null) {returnnull ؛ }. 3. عند كتابة محمل فئة مخصصة ، إذا لم يتم تعيين المحمل الأصل ، فما هو المحمل الأصل؟
عندما لا يتم تحديد محمل الفئة الأصل ، يتم استخدام محمل فئة النظام افتراضيًا. قد لا يفهم بعض الأشخاص ، الآن دعونا نلقي نظرة على تنفيذ الكود المقابل لـ JDK. كما نعلم جميعًا ، نكتب محمل فئة مخصصة بشكل مباشر أو غير مباشر من فئة java.lang.classloader الملخص. يتم تنفيذ المنشئ الافتراضي المقابل بدون معلمات على النحو التالي:
// Excerpt من java.lang.classloader.javaprotected classloader () {securityManager Security = system.getSecurityManager () ؛ if (Security! = null) {security.CheckCreateClassLoader () ؛ } this.parent = getSystemClassLoader () ؛ تهيئة = صواب ؛}دعونا نلقي نظرة على التنفيذ المقابل لطريقة getSystemClassLaStloader ():
privatestaticynchronizedvoid initsystemclasslader () {// ... sun.misc.launcher l = sun.misc.launcher.getlauncher () ؛ scl = l.getClassLoader () ؛ // ...}يمكننا كتابة رمز اختبار بسيط لاختباره:
System.out.println (sun.misc.launcher.getlauncher (). getClassLoader ()) ؛
الإخراج المقابل لهذا الجهاز هو كما يلي:
sun.misc.launcher$ appclasslassloader@197d257
لذلك ، يمكننا الآن أن نعتقد أنه عندما لا يحدد محمل الفئة المخصصة لوادر الفئة الأصل ، فإن محمل الفئة Parent الافتراضية هو محمل فئة النظام. في الوقت نفسه ، يمكننا استخلاص الاستنتاجات التالية:
لا يحدد محمل الفئة الفوري المعرفة من قبل المستخدم محمل الفئة الأصل ، ثم يمكن أيضًا تحميل الفئات في الأماكن الثلاثة التالية:
(1) فئات تحت <java_runtime_home>/lib
(2) فئات في الموقع المحدد بواسطة متغير النظام java.ext.dir
(3) فئات ضمن مسار فئة المشروع الحالي أو في الموقع المحدد بواسطة متغير النظام java.class.path
4. عند كتابة محمل فئة مخصصة ، ماذا سيحدث إذا تم إجبار محمل الفئة الأصل على أن يكون فارغًا؟ إذا لم يتمكن محمل الفئة المخصصة من تحميل الفئة المحددة ، فهل ستفشل بالتأكيد؟
تنص مواصفات JVM على أنه إذا قام محمل الفئة المعرفة من قبل المستخدم بفرض محمل الفئة الأصل على NULL ، فسيتم ضبط محمل فئة بدء التشغيل تلقائيًا على محمل الفئة الأصل لعملية تحميل الفئة المعرفة من قبل المستخدم (تم تحليل هذه المشكلة من قبل). في الوقت نفسه ، يمكننا استخلاص الاستنتاجات التالية:
إذا لم يحدد محمل الفئة الفوري المعرفة من قبل المستخدم محمل الفئة الأصل ، فيمكنه أيضًا تحميل الفئة تحت <java_runtime_home>/lib ، ولكن لا يمكن تحميل الفئة تحت <Java_Runtime_Home>/lib/ext في هذا الوقت.
ملاحظة: تستند الاستنتاجات المستخلصة للأسئلة 3 و 4 إلى محمل الفئة المعرفة من قبل المستخدم والتي تواصل منطق التفويض الافتراضي لـ java.lang.classloader.loadClass (...). إذا قام المستخدم بتغيير منطق التفويض الافتراضي هذا ، فقد لا يكون الاستنتاج المستنتج أعلاه صالحًا. انظر السؤال 5 للحصول على التفاصيل.
5. ما هي نقاط الاهتمام العامة عند كتابة اللوادر المخصصة؟
(1) بشكل عام ، حاول عدم تجاوز منطق التفويض في طريقة LoadClass (...) الحالية. بشكل عام ، يتم ذلك في الإصدارات قبل JDK 1.2 ، واتضح أن القيام بذلك من المرجح أن يتسبب في عدم عمل محمل الفئة الافتراضي للنظام بشكل صحيح. في مواصفات JVM وتوثيق JDK (1.2 أو الأحدث) ، لا ينصح بأن يقوم المستخدمون بالكتابة فوق طريقة LoadClass (...). في المقابل ، يتم تذكير المطورين بوضوح بالكتابة فوق منطق FindClass (...) عند تطوير محمل فئة مخصص. خذ مثالاً للتحقق من المشكلة:
// user custom class loader ksrclassloader.java (الكتابة فوق loadclass منطق) publicClassWrongClassLoxtends ClassLoader {public class <؟ } الفئة المحمية <؟> FindClass (اسم السلسلة) يلقي classnotfoundException {// لنفترض هنا فقط إلى دليل محدد آخر غير المشروع D: /Library لتحميل رمز التنفيذ المحدد للفئة}}}من خلال التحليل السابق ، نعلم بالفعل أن الافتراضي لمحمل الفئة المعرفة من قبل المستخدم (krrisclassloader)
محمل الفئة المعترف به هو محمل فئة النظام ، ولكن استنتاجات الأنواع الأربعة من المشكلات غير صالحة الآن. يمكن للجميع
فقط اختبره ، الآن <java_runtime_home>/lib ، <java_runtime_home>/lib/ext و
لا يمكن تحميل الفصول الدراسية على مسار فئة البرنامج.
السؤال 5 رمز الاختبار 1
publicClass krrisclassloadertest {publicStaticVoid Main (string [] args) {try {drongclassloader loader = new rumberclassloader () ؛ class classloaded = loader.loadClass ("Beans.Account") ؛ System.out.println (classloaded.getName ()) ؛ System.out.println (classloaded.getClassLoader ()) ؛ } catch (استثناء e) {E.PrintStackTrace () ؛ }}}(ملاحظة: D: "فئات" الفاصوليا "
نتيجة الإخراج:
java.io.filenotfoundException: d: "classes" java "lang" object.class (لا يمكن للنظام العثور على المسار المحدد.) في java.io.fileinputstream.open (الطريقة الأصلية) في java.io.fileinputstream. rinkclassloader.findclass (krrisclassloader.java:40) في kringclassloader.loadclass (krestclassloader.java:29) على java.lang.classloader.loadClassinternal (classloader.java:319) على java.lang.classloader.defineClass1. java.lang.classloader.defineclass (classloader.java:620) في java.lang.classloader.defineclass (classloader.java:400) على errorclaslader.findclass (errunclaslader.java:43) krrisclasslactloadertest.main (ksrclassloadertest.java:27) استثناء في الموضوع "Main" java.lang.classloader.defineclass (classloader.java:400) في kringclassloader.findclass (krestclassloader.java:43) على errorclassloader.
هذا يعني أنه حتى لا يمكن تحميل java.lang.object من النوع المراد تحميله. من الواضح أن الأخطاء المنطقية المذكورة هنا ناتجة عن الكتابة فوق الحمل (...) بسيطة نسبيًا ، وقد تكون الأخطاء المنطقية التي تسببها في الواقع أكثر تعقيدًا.
السؤال 5 اختبار 2
// فئة فئة مخصصة للمستخدم ، krrisllassloader.java (لا تقم بالكتابة فوق loadclass منطق) publicClassWrongClassLoxtends classloader {فئة محمية <؟> FindClass (اسم السلسلة) Ristrows ClassNotFoundException {// لنفترض هنا فقط إلى دليل محدد بخلاف المشروع d:بعد تعديل رمز Loader المخصص kringclassloader.java ، قم بتشغيل رمز الاختبار ، ونتيجة الإخراج هي كما يلي:
Beans.AccountWrongClassloader@1C78E57
يوضح هذا أن Beans.Account يتم تحميله بنجاح ويتم تحميله بواسطة محمل Load Custom Loader.
لا أعتقد أن هناك أي حاجة لشرح أسباب ذلك ، ويجب أن تكون قادرًا على تحليله.
(2) قم بإعداد محمل الفئة الأصل بشكل صحيح من خلال تحليل السؤال 4 والسؤال 5 أعلاه. أنا شخصياً أعتقد أن هذه هي النقطة الأكثر أهمية عند تخصيص لوادر فئة المستخدمين ، ولكن غالبًا ما يتم تجاهلها أو تجاهلها بسهولة. مع تحليل رمز JDK السابق كأساس ، أعتقد أن الجميع يمكنهم إعطاء أي أمثلة الآن.
(3) تأكد من الصواب المنطقي لطريقة FindClass (سلسلة) ، وحاول فهم مهام التحميل بدقة ليتم إكمالها بواسطة محمل الفئة ليتم تحديدها ، والتأكد من أنه يمكن الحصول على محتوى Bytecode المقابل إلى أقصى حد.
6.
أولاً ، يمكنك استدعاء ClassLoader.getSystemClasslassloader () أو غيرها من الطرق للحصول على محمل فئة النظام (محمل فئة النظام ومواد فئة التمديد مشتقة من urlclassloader) ، ويمكنك الحصول عليها عن طريق استدعاء طريقة geturls () في urlclassloader ؛
ثانياً ، يمكنك عرض معلومات الإدخال مباشرة على ClassPath الحالي عن طريق الحصول على سمة النظام java.class.path. System.getProperty ("java.class.path")
7. كيفية تحديد المسارات التي يمكن تحميلها لوادر فئة التمديد القياسية؟
الطريقة الأولى:
حاول {url [] exturls = ((urlClassLoader) classloader.getSystemClassLoader (). getParent ()). geturls () ؛ لـ (int i = 0 ؛ i <exturls.length ؛ i ++) {system.out.println (exturls [i]) ؛ }} catch (استثناء e) {// ...}الإخراج المقابل لهذا الجهاز هو كما يلي:
ملف:/d: /demo/jdk1.5.0_09/jre/lib/ext/dnsns.jarfile:/d: /demo/jdk1.5.0_09/jre/lib/ext/localedata.jarfile :/d: /demo/jdk1.5.0_09/jre/lib/ext/sunjce_provider.jarfile:/d: /demo/jdk1.5.0_09/jre/lib/ext/sunpkcs11.jar