JavaRush /مدونة جافا /Random-AR /باستخدام JNDI في جافا
Анзор Кармов
مستوى
Санкт-Петербург

باستخدام JNDI في جافا

نشرت في المجموعة
مرحبًا! اليوم سوف نقدم لك JNDI. دعونا نكتشف ما هو، ولماذا هو مطلوب، وكيف يعمل، وكيف يمكننا العمل معه. وبعد ذلك، سنكتب اختبارًا لوحدة Spring Boot، وسنلعب بداخله باستخدام JNDI ذاته. استخدام JNDI في جافا - 1

مقدمة. خدمات التسمية والدليل

قبل الغوص في JNDI، دعونا نفهم ما هي خدمات التسمية والدليل. المثال الأكثر وضوحًا لمثل هذه الخدمة هو نظام الملفات الموجود على أي جهاز كمبيوتر أو كمبيوتر محمول أو هاتف ذكي. يدير نظام الملفات الملفات (بشكل غريب). يتم تجميع الملفات في مثل هذه الأنظمة في بنية شجرة. كل ملف له اسم كامل فريد، على سبيل المثال: C:\windows\notepad.exe. يرجى ملاحظة: اسم الملف الكامل هو المسار من نقطة الجذر (محرك الأقراص C) إلى الملف نفسه (notepad.exe). العقد الوسيطة في مثل هذه السلسلة هي الدلائل (دليل Windows). الملفات الموجودة داخل الدلائل لها سمات. على سبيل المثال، "مخفي"، "للقراءة فقط"، وما إلى ذلك. سيساعد الوصف التفصيلي لشيء بسيط مثل نظام الملفات على فهم تعريف خدمات التسمية والدليل بشكل أفضل. لذا، فإن خدمة الاسم والدليل هي نظام يدير تعيين العديد من الأسماء للعديد من الكائنات. في نظام الملفات لدينا، نتفاعل مع أسماء الملفات التي تخفي الكائنات، أي الملفات نفسها بتنسيقات مختلفة. في خدمة التسمية والدليل، يتم تنظيم الكائنات المسماة في بنية شجرة. وكائنات الدليل لها سمات. مثال آخر على خدمة الاسم والدليل هو DNS (نظام اسم المجال). يدير هذا النظام التعيين بين أسماء النطاقات التي يمكن قراءتها بواسطة الإنسان (على سبيل المثال، https://javarush.com/) وعناوين IP التي يمكن قراءتها بواسطة الجهاز (على سبيل المثال، 18.196.51.113). إلى جانب DNS وأنظمة الملفات، هناك الكثير من الخدمات الأخرى، مثل:

JNDI

JNDI، أو Java Naming and Directory Interface، هي واجهة برمجة تطبيقات Java للوصول إلى خدمات التسمية والدليل. JNDI عبارة عن واجهة برمجة تطبيقات توفر آلية موحدة لبرنامج Java للتفاعل مع خدمات التسمية والدليل المختلفة. تحت الغطاء، يتم تحقيق التكامل بين JNDI وأي خدمة معينة باستخدام واجهة مزود الخدمة (SPI). تسمح SPI بتوصيل خدمات التسمية والدليل المختلفة بشفافية، مما يسمح لتطبيق Java باستخدام JNDI API للوصول إلى الخدمات المتصلة. يوضح الشكل أدناه بنية JNDI: استخدام JNDI في جافا - 2

المصدر: دروس أوراكل جافا

JNDI. المعنى بكلمات بسيطة

السؤال الرئيسي هو: لماذا نحتاج إلى JNDI؟ هناك حاجة إلى JNDI حتى نتمكن من الحصول على كائن Java من بعض "تسجيل" الكائنات من كود Java باسم الكائن المرتبط بهذا الكائن. دعونا نقسم العبارة أعلاه إلى أطروحات حتى لا تربكنا كثرة الكلمات المكررة:
  1. في النهاية نحتاج إلى الحصول على كائن Java.
  2. سوف نحصل على هذا الكائن من بعض التسجيل.
  3. هناك مجموعة من الكائنات في هذا التسجيل.
  4. كل كائن في هذا التسجيل له اسم فريد.
  5. للحصول على كائن من السجل، يجب علينا تمرير اسم في طلبنا. وكأنه يقول: أعطني ما عندك تحت اسم كذا وكذا.
  6. لا يمكننا قراءة الكائنات حسب أسمائها من السجل فحسب، بل يمكننا أيضًا حفظ الكائنات في هذا السجل بأسماء معينة (بطريقة ما ينتهي بها الأمر هناك).
لذلك، لدينا نوع من التسجيل، أو تخزين الكائنات، أو شجرة JNDI. بعد ذلك، باستخدام مثال، دعونا نحاول فهم معنى JNDI. تجدر الإشارة إلى أنه يتم استخدام JNDI في الغالب في تطوير المؤسسات. وتعمل مثل هذه التطبيقات داخل بعض خوادم التطبيقات. يمكن أن يكون هذا الخادم أحد خوادم تطبيقات Java EE، أو حاوية servlet مثل Tomcat، أو أي حاوية أخرى. عادةً ما يوجد سجل الكائنات نفسه، أي شجرة JNDI، داخل خادم التطبيق هذا. هذا الأخير ليس ضروريًا دائمًا (يمكنك الحصول على مثل هذه الشجرة محليًا)، ولكنه الأكثر شيوعًا. يمكن إدارة شجرة JNDI بواسطة شخص خاص (مسؤول النظام أو متخصص في DevOps) والذي سيقوم "بحفظ الكائنات في السجل" بأسمائها. عندما يكون تطبيقنا وشجرة JNDI موجودين في نفس الحاوية، يمكننا بسهولة الوصول إلى أي كائن Java مخزّن في مثل هذا السجل. علاوة على ذلك، يمكن وضع السجل وتطبيقنا في حاويات مختلفة وحتى على أجهزة فعلية مختلفة. حتى أن JNDI يسمح لك بالوصول إلى كائنات Java عن بعد. حالة نموذجية. يقوم مسئول خادم Java EE بوضع كائن في السجل يقوم بتخزين المعلومات الضرورية للاتصال بقاعدة البيانات. وفقًا لذلك، للعمل مع قاعدة البيانات، سنطلب ببساطة الكائن المطلوب من شجرة JNDI ونعمل معه. أنها مريحة للغاية. تكمن الراحة أيضًا في حقيقة وجود بيئات مختلفة في تطوير المؤسسات. هناك خوادم إنتاج، وهناك خوادم اختبار (وغالبًا ما يكون هناك أكثر من خادم اختبار واحد). بعد ذلك، من خلال وضع كائن للاتصال بقاعدة البيانات على كل خادم داخل JNDI واستخدام هذا الكائن داخل تطبيقنا، لن نضطر إلى تغيير أي شيء عند نشر تطبيقنا من خادم (اختبار، إصدار) إلى آخر. سيكون هناك إمكانية الوصول إلى قاعدة البيانات في كل مكان. المثال، بالطبع، مبسط إلى حد ما، لكنني آمل أن يساعدك على فهم سبب الحاجة إلى JNDI بشكل أفضل. بعد ذلك، سوف نتعرف على JNDI في Java عن كثب، مع بعض عناصر الهجوم.

واجهة برمجة التطبيقات JNDI

يتم توفير JNDI ضمن منصة Java SE. لاستخدام JNDI، يجب عليك استيراد فئات JNDI، بالإضافة إلى واحد أو أكثر من موفري الخدمة للوصول إلى خدمات التسمية والدليل. يتضمن JDK مقدمي الخدمات للخدمات التالية:
  • بروتوكول الوصول إلى الدليل خفيف الوزن (LDAP)؛
  • بنية وسيط طلب الكائنات المشتركة (CORBA)؛
  • خدمة اسم خدمات الكائنات المشتركة (COS)؛
  • سجل استدعاء الأسلوب عن بعد لـ Java (RMI)؛
  • خدمة اسم النطاق (DNS).
ينقسم كود JNDI API إلى عدة حزم:
  • javax.naming;
  • javax.naming.directory;
  • javax.naming.ldap;
  • javax.naming.event;
  • javax.naming.spi.
سنبدأ تقديمنا لـ JNDI بواجهتين - الاسم والسياق، اللتين تحتويان على وظائف JNDI الرئيسية

اسم الواجهة

تسمح لك واجهة الاسم بالتحكم في أسماء المكونات بالإضافة إلى بناء جملة تسمية JNDI. في JNDI، يتم تنفيذ كافة عمليات الاسم والدليل بالنسبة للسياق. لا توجد جذور مطلقة. لذلك، تحدد JNDI نصًا أوليًا، والذي يوفر نقطة بداية لعمليات التسمية والدليل. بمجرد الوصول إلى السياق الأولي، يمكن استخدامه للبحث عن الكائنات والسياقات الأخرى.
Name objectName = new CompositeName("java:comp/env/jdbc");
في الكود أعلاه، حددنا بعض الأسماء التي يقع تحتها كائن ما (قد لا يكون موجودًا، لكننا نعول عليه). هدفنا النهائي هو الحصول على مرجع لهذا الكائن واستخدامه في برنامجنا. لذلك، يتكون الاسم من عدة أجزاء (أو رموز)، مفصولة بشرطة مائلة. تسمى هذه الرموز المميزة السياقات. الأول هو ببساطة السياق، وجميع السياقات اللاحقة هي سياق فرعي (يشار إليه فيما يلي باسم السياق الفرعي). من الأسهل فهم السياقات إذا كنت تعتقد أنها مشابهة للأدلة أو الأدلة، أو مجرد مجلدات عادية. السياق الجذر هو المجلد الجذر. السياق الفرعي هو مجلد فرعي. يمكننا رؤية جميع المكونات (السياق والسياقات الفرعية) لاسم معين عن طريق تشغيل الكود التالي:
Enumeration<String> elements = objectName.getAll();
while(elements.hasMoreElements()) {
  System.out.println(elements.nextElement());
}
سيكون الإخراج كما يلي:

java:comp
env
jdbc
يُظهر الإخراج أن الرموز المميزة في الاسم مفصولة عن بعضها البعض بشرطة مائلة (ومع ذلك، ذكرنا هذا). كل رمز مميز له فهرس خاص به. تبدأ فهرسة الرمز المميز عند 0. يحتوي السياق الجذر على فهرس صفر، والسياق التالي له فهرس 1، والسياق التالي له فهرس 2، وما إلى ذلك. يمكننا الحصول على اسم السياق الفرعي من خلال فهرسه:
System.out.println(objectName.get(1)); // -> env
يمكننا أيضًا إضافة رموز إضافية (إما في النهاية أو في موقع محدد في الفهرس):
objectName.add("sub-context"); // Добавит sub-context в конец
objectName.add(0, "context"); // Добавит context в налачо
يمكن العثور على قائمة كاملة بالطرق في الوثائق الرسمية .

سياق الواجهة

تحتوي هذه الواجهة على مجموعة من الثوابت لتهيئة السياق، بالإضافة إلى مجموعة من الأساليب لإنشاء وحذف السياقات، وربط الكائنات بالاسم، والبحث عن الكائنات واسترجاعها. دعونا نلقي نظرة على بعض العمليات التي يتم تنفيذها باستخدام هذه الواجهة. الإجراء الأكثر شيوعًا هو البحث عن كائن بالاسم. ويتم ذلك باستخدام الطرق:
  • Object lookup(String name)
  • Object lookup(Name name)
يتم ربط كائن بالاسم باستخدام الطرق bind:
  • void bind(Name name, Object obj)
  • void bind(String name, Object obj)
ستقوم كلا الطريقتين بربط اسم الاسم بالكائن، Object ويتم تنفيذ العملية العكسية للربط - فك ربط كائن من الاسم، باستخدام الطرق التالية unbind:
  • void unbind(Name name)
  • void unbind(String name)
تتوفر قائمة كاملة بالطرق على موقع التوثيق الرسمي .

سياق أولي

InitialContextهي فئة تمثل العنصر الجذر لشجرة JNDI وتنفذ Context. تحتاج إلى البحث عن الكائنات بالاسم داخل شجرة JNDI بالنسبة لعقدة معينة. يمكن أن تكون العقدة الجذرية للشجرة بمثابة هذه العقدة InitialContext. حالة الاستخدام النموذجية لـ JNDI هي:
  • يحصل InitialContext.
  • يُستخدم InitialContextلاسترداد الكائنات بالاسم من شجرة JNDI.
هناك عدة طرق للحصول عليه InitialContext. كل هذا يتوقف على البيئة التي يوجد بها برنامج Java. على سبيل المثال، إذا كان برنامج Java وشجرة JNDI يعملان داخل نفس خادم التطبيق، فمن InitialContextالسهل جدًا الحصول على:
InitialContext context = new InitialContext();
إذا لم يكن الأمر كذلك، يصبح الحصول على السياق أكثر صعوبة قليلاً. في بعض الأحيان يكون من الضروري تمرير قائمة بخصائص البيئة لتهيئة السياق:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
    "com.sun.jndi.fscontext.RefFSContextFactory");

Context ctx = new InitialContext(env);
يوضح المثال أعلاه إحدى الطرق الممكنة لتهيئة السياق ولا يحمل أي حمل دلالي آخر. ليست هناك حاجة للتعمق في الكود بالتفصيل.

مثال على استخدام JNDI داخل اختبار وحدة SpringBoot

أعلاه، قلنا أنه لكي تتفاعل JNDI مع خدمة التسمية والدليل، من الضروري أن يكون لديك SPI (واجهة موفر الخدمة) في متناول اليد، والتي سيتم من خلالها تنفيذ التكامل بين Java وخدمة التسمية. يأتي JDK القياسي مزودًا بالعديد من واجهات برمجة التطبيقات (SPIs) المختلفة (أدرجناها أعلاه)، وكل واحدة منها ليست ذات أهمية كبيرة لأغراض العرض التوضيحي. يعد رفع تطبيق JNDI وJava داخل الحاوية أمرًا مثيرًا للاهتمام إلى حد ما. ومع ذلك، فإن مؤلف هذه المقالة شخص كسول، لذا لتوضيح كيفية عمل JNDI، اختار المسار الأقل مقاومة: قم بتشغيل JNDI داخل اختبار وحدة تطبيق SpringBoot والوصول إلى سياق JNDI باستخدام اختراق صغير من Spring Framework. لذلك، خطتنا:
  • لنكتب مشروع Spring Boot فارغًا.
  • لنقم بإنشاء اختبار وحدة داخل هذا المشروع.
  • سنوضح داخل الاختبار العمل مع JNDI:
    • الوصول إلى السياق؛
    • ربط (ربط) بعض الكائنات تحت اسم ما في JNDI؛
    • الحصول على الكائن باسمه (بحث)؛
    • دعونا نتحقق من أن الكائن ليس فارغًا.
لنبدأ بالترتيب. ملف->جديد->مشروع... باستخدام JNDI في جافا - 3 بعد ذلك، حدد عنصر تهيئة الربيع : استخدام JNDI في جافا - 4املأ البيانات الوصفية حول المشروع: باستخدام JNDI في جافا - 5ثم حدد مكونات Spring Framework المطلوبة. سنقوم بربط بعض كائنات DataSource، لذلك نحتاج إلى مكونات للعمل مع قاعدة البيانات:
  • واجهة برمجة تطبيقات JDBC؛
  • قاعدة بيانات H2
باستخدام JNDI في جافا - 6لنحدد الموقع في نظام الملفات: باستخدام JNDI في جافا - 7ويتم إنشاء المشروع. في الواقع، تم إنشاء اختبار وحدة واحدة تلقائيًا لنا، والذي سنستخدمه لأغراض العرض التوضيحي. فيما يلي بنية المشروع والاختبار الذي نحتاجه: باستخدام JNDI في جافا - 8لنبدأ بكتابة التعليمات البرمجية داخل اختبار contextLoads. اختراق صغير من الربيع، والذي تمت مناقشته أعلاه - هذا هو الفصل SimpleNamingContextBuilder. تم تصميم هذه الفئة لرفع مستوى JNDI بسهولة داخل اختبارات الوحدة أو التطبيقات المستقلة. لنكتب الكود للحصول على السياق:
final SimpleNamingContextBuilder simpleNamingContextBuilder
       = new SimpleNamingContextBuilder();
simpleNamingContextBuilder.activate();

final InitialContext context = new InitialContext();
سيسمح لنا أول سطرين من التعليمات البرمجية بتهيئة سياق JNDI بسهولة لاحقًا. بدونها، InitialContextسيتم طرح استثناء عند إنشاء مثيل: javax.naming.NoInitialContextException. تنصل. الفئة SimpleNamingContextBuilderهي فئة مهملة. ويهدف هذا المثال إلى إظهار كيف يمكنك العمل مع JNDI. هذه ليست أفضل الممارسات لاستخدام JNDI داخل اختبارات الوحدة. يمكن القول أن هذا بمثابة عكاز لبناء سياق وإظهار ربط الكائنات واسترجاعها من JNDI. بعد تلقي سياق، يمكننا استخراج الكائنات منه أو البحث عن كائنات في السياق. لا توجد كائنات في JNDI حتى الآن، لذا سيكون من المنطقي وضع شيء ما هناك. على سبيل المثال، DriverManagerDataSource:
context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));
في هذا السطر، قمنا بربط كائن الفئة DriverManagerDataSourceبالاسم java:comp/env/jdbc/datasource. بعد ذلك، يمكننا الحصول على الكائن من السياق بالاسم. ليس لدينا خيار سوى الحصول على الكائن الذي وضعناه للتو، لأنه لا توجد كائنات أخرى في السياق =(
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
الآن دعونا نتحقق من أن مصدر البيانات الخاص بنا لديه اتصال (الاتصال أو الاتصال أو الاتصال هو فئة Java مصممة للعمل مع قاعدة البيانات):
assert ds.getConnection() != null;
System.out.println(ds.getConnection());
إذا فعلنا كل شيء بشكل صحيح، فإن الناتج سيكون مثل هذا:

conn1: url=jdbc:h2:mem:mydb user=
تجدر الإشارة إلى أن بعض أسطر التعليمات البرمجية قد تؤدي إلى استثناءات. يتم طرح الأسطر التالية javax.naming.NamingException:
  • simpleNamingContextBuilder.activate()
  • new InitialContext()
  • context.bind(...)
  • context.lookup(...)
وعند العمل مع فصل دراسي DataSourceيمكن طرحه java.sql.SQLException. في هذا الصدد، من الضروري تنفيذ التعليمات البرمجية داخل كتلة try-catch، أو الإشارة في توقيع وحدة الاختبار إلى أنها يمكنها طرح استثناءات. إليك الكود الكامل لفئة الاختبار:
@SpringBootTest
class JndiExampleApplicationTests {

    @Test
    void contextLoads() {
        try {
            final SimpleNamingContextBuilder simpleNamingContextBuilder
                    = new SimpleNamingContextBuilder();
            simpleNamingContextBuilder.activate();

            final InitialContext context = new InitialContext();

            context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));

            final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");

            assert ds.getConnection() != null;
            System.out.println(ds.getConnection());

        } catch (SQLException | NamingException e) {
            e.printStackTrace();
        }
    }
}
بعد إجراء الاختبار، يمكنك رؤية السجلات التالية:

o.s.m.jndi.SimpleNamingContextBuilder    : Activating simple JNDI environment
o.s.mock.jndi.SimpleNamingContext        : Static JNDI binding: [java:comp/env/jdbc/datasource] = [org.springframework.jdbc.datasource.DriverManagerDataSource@4925f4f5]
conn1: url=jdbc:h2:mem:mydb user=

خاتمة

اليوم نظرنا إلى JNDI. لقد تعلمنا ما هي خدمات التسمية والدليل، وأن JNDI عبارة عن واجهة برمجة تطبيقات Java تسمح لك بالتفاعل بشكل موحد مع خدمات مختلفة من برنامج Java. على وجه التحديد، بمساعدة JNDI، يمكننا تسجيل الكائنات في شجرة JNDI تحت اسم معين واستلام هذه الكائنات نفسها بالاسم. كمهمة إضافية، يمكنك تشغيل مثال لكيفية عمل JNDI. قم بربط كائن آخر في السياق، ثم قم بقراءة هذا الكائن بالاسم.
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION