JavaRush /בלוג Java /Random-HE /שימוש ב-JNDI ב-Java
Анзор Кармов
רָמָה
Санкт-Петербург

שימוש ב-JNDI ב-Java

פורסם בקבוצה
שלום! היום נציג בפניכם את JNDI. בואו לגלות מה זה, למה זה נחוץ, איך זה עובד, איך אנחנו יכולים לעבוד עם זה. ואז נכתוב מבחן יחידת Spring Boot, שבתוכו נשחק עם ה-JNDI הזה. שימוש ב-JNDI ב-Java - 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 API לגישה לשירותי שמות וספריות. JNDI הוא API המספק מנגנון אחיד לתוכנת Java לקיים אינטראקציה עם שירותי שמות וספריות שונים. מתחת למכסה המנוע, האינטגרציה בין JNDI לכל שירות נתון מתבצעת באמצעות ממשק ספק שירות (SPI). SPI מאפשר חיבור שקוף לשירותי שמות וספריות שונים, מה שמאפשר ליישום Java להשתמש ב-JNDI API כדי לגשת לשירותים המחוברים. האיור שלהלן ממחיש את ארכיטקטורת JNDI: שימוש ב-JNDI ב-Java - 2

מקור: Oracle Java Tutorials

JNDI. הכוונה במילים פשוטות

השאלה העיקרית היא: למה אנחנו צריכים JNDI? יש צורך ב-JNDI כדי שנוכל לקבל אובייקט Java מ"רישום" כלשהו של אובייקטים מקוד ג'אווה לפי שם האובייקט המחובר לאובייקט זה. בואו נחלק את ההצהרה לעיל לתזות כדי ששפע המילים החוזרות לא יבלבלו אותנו:
  1. בסופו של דבר אנחנו צריכים להשיג אובייקט Java.
  2. נקבל את האובייקט הזה מאיזה רישום.
  3. יש חבורה של אובייקטים ברישום הזה.
  4. לכל אובייקט ברישום זה יש שם ייחודי.
  5. כדי לקבל חפץ מהרישום, עלינו להעביר שם בבקשה שלנו. כאילו אומר: "בבקשה תן לי מה שיש לך תחת שם כזה ואחר".
  6. אנחנו יכולים לא רק לקרוא אובייקטים לפי שמם מהרישום, אלא גם לשמור אובייקטים ברישום זה תחת שמות מסוימים (איכשהו הם מגיעים לשם).
אז, יש לנו איזשהו רישום, או אחסון אובייקטים, או JNDI Tree. לאחר מכן, בעזרת דוגמה, בואו ננסה להבין את המשמעות של JNDI. ראוי לציין שלרוב JNDI משמש בפיתוח Enterprise. ויישומים כאלה עובדים בתוך שרת יישומים כלשהו. שרת זה יכול להיות שרת יישומים של Java EE, או מיכל servlet כמו Tomcat, או כל מיכל אחר. רישום האובייקטים עצמו, כלומר עץ JNDI, ממוקם בדרך כלל בתוך שרת יישומים זה. זה האחרון לא תמיד הכרחי (אתה יכול לקבל עץ כזה באופן מקומי), אבל זה אופייני ביותר. JNDI Tree יכול להיות מנוהל על ידי אדם מיוחד (מנהל מערכת או מומחה DevOps) אשר "ישמור ברישום" אובייקטים עם שמם. כאשר האפליקציה שלנו ועץ JNDI ממוקמים יחד בתוך אותו מיכל, אנו יכולים לגשת בקלות לכל אובייקט Java שמאוחסן ברישום כזה. יתרה מכך, הרישום והאפליקציה שלנו יכולים להיות ממוקמים במיכלים שונים ואפילו במכונות פיזיות שונות. JNDI גם אז מאפשר לך לגשת לאובייקטי Java מרחוק. מקרה טיפוסי. מנהל שרת Java EE מציב ברישום אובייקט המאחסן את המידע הדרוש לחיבור למסד הנתונים. בהתאם לכך, כדי לעבוד עם בסיס הנתונים, פשוט נבקש את האובייקט הנדרש מעץ JNDI ונעבוד איתו. זה מאוד נוח. הנוחות טמונה גם בעובדה שבפיתוח ארגוני קיימות סביבות שונות. יש שרתי ייצור, ויש שרתי בדיקה (ולעתים קרובות יש יותר משרת בדיקה אחד). לאחר מכן, על ידי הצבת אובייקט לחיבור למסד הנתונים בכל שרת בתוך JNDI ושימוש באובייקט זה בתוך האפליקציה שלנו, לא נצטרך לשנות דבר בעת פריסת האפליקציה שלנו משרת אחד (בדיקה, שחרור) לאחר. תהיה גישה למסד הנתונים בכל מקום. הדוגמה, כמובן, מעט מפושטת, אבל אני מקווה שהיא תעזור לך להבין טוב יותר מדוע יש צורך ב-JNDI. לאחר מכן, נכיר את JNDI ב-Java יותר מקרוב, עם כמה אלמנטים של תקיפה.

JNDI API

JNDI מסופק בתוך פלטפורמת Java SE. כדי להשתמש ב-JNDI, עליך לייבא מחלקות JNDI, כמו גם ספק שירות אחד או יותר כדי לגשת לשירותי שמות וספריות. ה-JDK כולל ספקי שירותים עבור השירותים הבאים:
  • פרוטוקול גישה למדריך קל משקל (LDAP);
  • Common Object Request Broker Architecture (CORBA);
  • שירות שמות של Common Object Services (COS);
  • Registration Remote Method Invocation (RMI) של Java;
  • שירות שמות דומיין (DNS).
קוד ה-API של JNDI מחולק למספר חבילות:
  • javax.name;
  • javax.naming.directory;
  • javax.naming.ldap;
  • javax.naming.event;
  • javax.naming.spi.
נתחיל את ההקדמה שלנו ל-JNDI עם שני ממשקים - שם והקשר, המכילים פונקציונליות מרכזית של JNDI

שם ממשק

ממשק השם מאפשר לך לשלוט בשמות הרכיבים כמו גם בתחביר מתן השמות של JNDI. ב-JNDI, כל פעולות השם והספרייה מבוצעות ביחס להקשר. אין שורשים מוחלטים. לכן, JNDI מגדיר InitialContext, המספק נקודת התחלה לפעולות שמות וספריות. לאחר גישה להקשר הראשוני, ניתן להשתמש בו כדי לחפש אובייקטים והקשרים אחרים.
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

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 (Service Provider Interface) בהישג יד, בעזרתו תתבצע אינטגרציה בין Java לשירות השמות. ה-JDK הסטנדרטי מגיע עם מספר SPIs שונים (מנינו אותם למעלה), שכל אחד מהם אינו מעניין במיוחד למטרות הדגמה. העלאת אפליקציית JNDI ו-Java בתוך קונטיינר היא קצת מעניינת. עם זאת, מחבר המאמר הזה הוא אדם עצלן, אז כדי להדגים איך JNDI עובד, הוא בחר בדרך של ההתנגדות הקטנה ביותר: הפעל את JNDI בתוך בדיקת יחידת יישום SpringBoot וגישה להקשר JNDI באמצעות פריצה קטנה מ-Spring Framework. אז התוכנית שלנו:
  • בואו נכתוב פרויקט Spring Boot ריק.
  • בואו ניצור מבחן יחידה בתוך הפרויקט הזה.
  • בתוך המבחן נדגים עבודה עם JNDI:
    • לקבל גישה להקשר;
    • bind (bind) אובייקט כלשהו תחת שם כלשהו ב-JNDI;
    • קבל את האובייקט לפי שמו (חיפוש);
    • בוא נבדוק שהאובייקט אינו null.
בואו נתחיל לפי הסדר. קובץ->חדש->פרויקט... שימוש ב-JNDI ב-Java - 3 לאחר מכן, בחר את הפריט Spring Initializr : שימוש ב-JNDI ב-Java - 4מלא את המטא-נתונים לגבי הפרויקט: שימוש ב-JNDI ב-Java - 5לאחר מכן בחר את רכיבי Spring Framework הנדרשים. נאגד כמה אובייקטים של DataSource, אז אנחנו צריכים רכיבים כדי לעבוד עם מסד הנתונים:
  • JDBC API;
  • H2 DDatabase.
שימוש ב-JNDI ב-Java - 6בואו נקבע את המיקום במערכת הקבצים: שימוש ב-JNDI ב-Java - 7והפרויקט נוצר. למעשה נוצרה עבורנו מבחן יחידה אחת באופן אוטומטי, שבה נשתמש למטרות הדגמה. להלן מבנה הפרויקט והבדיקה שאנו צריכים: שימוש ב-JNDI ב-Java - 8בואו נתחיל לכתוב קוד בתוך מבחן contextLoads. פריצה קטנה מ-Spring, שנדונה לעיל, היא הכיתה 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");
עכשיו בואו נבדוק שיש ל-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 API המאפשר לך ליצור אינטראקציה אחידה עם שירותים שונים מתוכנת Java. כלומר, בעזרת JNDI, אנו יכולים להקליט אובייקטים בעץ JNDI תחת שם מסוים ולקבל את אותם אובייקטים לפי השם. כמטלת בונוס, אתה יכול להריץ דוגמה לאופן שבו JNDI עובד. קשר אובייקט אחר לתוך ההקשר, ולאחר מכן קרא את האובייקט הזה לפי השם.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION