JavaRush /Java Blogu /Random-AZ /Java-da JNDI-dən istifadə
Анзор Кармов
Səviyyə
Санкт-Петербург

Java-da JNDI-dən istifadə

Qrupda dərc edilmişdir
Salam! Bu gün sizi JNDI ilə tanış edəcəyik. Gəlin bunun nə olduğunu, nə üçün lazım olduğunu, necə işlədiyini, onunla necə işləyə biləcəyimizi öyrənək. Və sonra biz bu JNDI ilə oynayacağımız Spring Boot vahid testini yazacağıq. Java-da JNDI-dən istifadə - 1

Giriş. Adlandırma və Kataloq Xidmətləri

JNDI-yə girməzdən əvvəl gəlin adlandırma və kataloq xidmətlərinin nə olduğunu anlayaq. Belə bir xidmətin ən bariz nümunəsi istənilən PC, noutbuk və ya smartfonda fayl sistemidir. Fayl sistemi (qəribə də olsa) faylları idarə edir. Belə sistemlərdəki fayllar ağac strukturunda qruplaşdırılır. Hər bir faylın unikal tam adı var, məsələn: C:\windows\notepad.exe. Nəzərə alın: tam fayl adı bəzi kök nöqtəsindən (C sürücüsü) faylın özünə (notepad.exe) gedən yoldur. Belə bir zəncirdəki ara qovşaqlar kataloqlardır (windows kataloqu). Kataloqlardakı fayllar atributlara malikdir. Məsələn, "Gizli", "Yalnız oxumaq üçün" və s. Fayl sistemi kimi sadə bir şeyin ətraflı təsviri ad və kataloq xidmətlərinin tərifini daha yaxşı başa düşməyə kömək edəcəkdir. Beləliklə, ad və kataloq xidməti bir çox adların bir çox obyektlə xəritələşdirilməsini idarə edən bir sistemdir. Fayl sistemimizdə obyektləri gizlədən fayl adları ilə - müxtəlif formatlarda olan faylların özləri ilə qarşılıqlı əlaqədə oluruq. Adlandırma və kataloq xidmətində adlandırılmış obyektlər ağac strukturunda təşkil edilir. Və kataloq obyektlərinin atributları var. Ad və kataloq xidmətinin başqa bir nümunəsi DNS-dir (Domain Name System). Bu sistem insan tərəfindən oxuna bilən domen adları (məsələn, https://javarush.com/) və maşın tərəfindən oxuna bilən IP ünvanları (məsələn, 18.196.51.113) arasında xəritələşməni idarə edir. DNS və fayl sistemlərindən başqa, bir çox başqa xidmətlər var, məsələn:

JNDI

JNDI və ya Java Adlandırma və Kataloq İnterfeysi, adlandırma və kataloq xidmətlərinə daxil olmaq üçün Java API-dir. JNDI Java proqramının müxtəlif adlandırma və kataloq xidmətləri ilə qarşılıqlı əlaqədə olması üçün vahid mexanizm təmin edən API-dir. Başlıq altında, JNDI və hər hansı bir xidmət arasında inteqrasiya Xidmət Provayderi İnterfeysi (SPI) istifadə edərək həyata keçirilir. SPI müxtəlif adlandırma və kataloq xidmətlərini şəffaf şəkildə birləşdirməyə imkan verir, Java tətbiqinə qoşulmuş xidmətlərə daxil olmaq üçün JNDI API-dən istifadə etməyə imkan verir. Aşağıdakı rəqəm JNDI arxitekturasını göstərir: Java-da JNDI-dən istifadə - 2

Mənbə: Oracle Java Dərslikləri

JNDI. Sadə sözlərlə məna

Əsas sual budur: niyə bizə JNDI lazımdır? JNDI ona görə lazımdır ki, Java kodundan bəzi obyektlərin “Qeydiyyatı”ndan bu obyektə bağlı obyektin adı ilə Java obyekti əldə edə bilək. Təkrarlanan sözlərin çoxluğu bizi çaşdırmasın deyə yuxarıdakı ifadəni tezislərə bölək:
  1. Nəhayət, Java obyektini əldə etməliyik.
  2. Bu obyekti bəzi registrdən alacağıq.
  3. Bu reyestrdə çoxlu obyektlər var.
  4. Bu reyestrdəki hər bir obyektin özünəməxsus adı var.
  5. Reyestrdən bir obyekt əldə etmək üçün sorğumuzda ad qeyd etməliyik. Sanki deyirsən: “Zəhmət olmasa, filan adda nə varsa mənə ver”.
  6. Biz təkcə reyestrdən obyektləri öz adı ilə oxuya bilmirik, həm də bu reyestrdəki obyektləri müəyyən adlar altında saxlaya bilirik (birtəhər orada bitirlər).
Beləliklə, bir növ reyestrimiz, obyekt saxlamamız və ya JNDI Ağacımız var. Sonra, bir nümunə istifadə edərək, JNDI-nin mənasını anlamağa çalışaq. Qeyd etmək lazımdır ki, JNDI əksər hallarda Enterprise inkişafında istifadə olunur. Və belə proqramlar hansısa proqram serverinin daxilində işləyir. Bu server bəzi Java EE Tətbiq Serveri və ya Tomcat kimi servlet konteyneri və ya hər hansı digər konteyner ola bilər. Obyekt reyestrinin özü, yəni JNDI Ağacı adətən bu proqram serverinin daxilində yerləşir. Sonuncu həmişə lazım deyil (yerli olaraq belə bir ağac ola bilər), lakin ən tipikdir. JNDI Ağacı obyektləri öz adları ilə “reyestrdə saxlayacaq” xüsusi şəxs (sistem administratoru və ya DevOps mütəxəssisi) tərəfindən idarə oluna bilər. Tətbiqimiz və JNDI Ağacı eyni konteynerin içərisində birlikdə yerləşdikdə, belə bir reyestrdə saxlanılan istənilən Java obyektinə asanlıqla daxil ola bilərik. Üstəlik, reyestr və tətbiqimiz müxtəlif konteynerlərdə və hətta müxtəlif fiziki maşınlarda yerləşdirilə bilər. JNDI hətta o zaman Java obyektlərinə uzaqdan daxil olmağa imkan verir. Tipik hal. Java EE server inzibatçısı reyestrdə verilənlər bazasına qoşulmaq üçün lazımi məlumatları saxlayan obyekti yerləşdirir. Müvafiq olaraq, verilənlər bazası ilə işləmək üçün biz sadəcə JNDI ağacından tələb olunan obyekti tələb edəcəyik və onunla işləyəcəyik. Çox rahatdır. Rahatlıq həm də müəssisənin inkişafında müxtəlif mühitlərin olmasındadır. İstehsal serverləri və test serverləri var (və çox vaxt 1-dən çox test serveri var). Sonra JNDI daxilindəki hər bir serverə verilənlər bazasına qoşulmaq üçün obyekt yerləşdirməklə və bu obyekti tətbiqimizdə istifadə etməklə, tətbiqimizi bir serverdən (test, buraxılış) digərinə yerləşdirərkən heç nəyi dəyişmək məcburiyyətində qalmayacağıq. Hər yerdə verilənlər bazasına giriş olacaq. Nümunə, əlbəttə ki, bir qədər sadələşdirilmişdir, lakin ümid edirəm ki, JNDI-nin nə üçün lazım olduğunu daha yaxşı başa düşməyə kömək edəcək. Sonra, bəzi hücum elementləri ilə Java-da JNDI ilə daha yaxından tanış olacağıq.

JNDI API

JNDI Java SE platforması daxilində təmin edilir. JNDI-dən istifadə etmək üçün siz JNDI siniflərini, eləcə də adlandırma və kataloq xidmətlərinə daxil olmaq üçün bir və ya bir neçə xidmət təminatçısını idxal etməlisiniz. JDK-ya aşağıdakı xidmətlər üçün xidmət təminatçıları daxildir:
  • Yüngül Kataloq Giriş Protokolu (LDAP);
  • Ümumi Obyekt Sorğu Broker Arxitekturası (CORBA);
  • Common Object Services (COS) ad xidməti;
  • Java Remote Method Invocation (RMI) Registry;
  • Domain Name Service (DNS).
JNDI API kodu bir neçə paketə bölünür:
  • javax.naming;
  • javax.naming.directory;
  • javax.naming.ldap;
  • javax.naming.event;
  • javax.naming.spi.
JNDI ilə tanışlığımıza iki interfeyslə başlayacağıq - əsas JNDI funksionallığını ehtiva edən Ad və Kontekst

İnterfeys Adı

Ad interfeysi komponent adlarını, eləcə də JNDI adlandırma sintaksisini idarə etməyə imkan verir. JNDI-də bütün ad və kataloq əməliyyatları kontekstə nisbətən yerinə yetirilir. Mütləq köklər yoxdur. Buna görə də, JNDI adlandırma və kataloq əməliyyatları üçün başlanğıc nöqtəsini təmin edən InitialContext-i müəyyən edir. İlkin kontekstə daxil olduqdan sonra o, obyektləri və digər kontekstləri axtarmaq üçün istifadə edilə bilər.
Name objectName = new CompositeName("java:comp/env/jdbc");
Yuxarıdakı kodda biz hansısa obyektin yerləşdiyi bir ad təyin etdik (yerləşə bilməz, amma biz ona ümid edirik). Bizim son məqsədimiz bu obyektə istinad əldə etmək və onu proqramımızda istifadə etməkdir. Beləliklə, ad bir-birindən kəsik işarəsi ilə ayrılmış bir neçə hissədən (və ya işarələrdən) ibarətdir. Belə işarələrə kontekstlər deyilir. Birincisi sadəcə kontekstdir, sonrakılar isə alt kontekstdir (bundan sonra alt kontekst adlandırılacaq). Kontekstləri qovluqlara və ya qovluqlara və ya sadəcə adi qovluqlara oxşar hesab edirsinizsə, onları başa düşmək daha asandır. Kök kontekst kök qovluqdur. Alt kontekst alt qovluqdur. Aşağıdakı kodu işlətməklə verilmiş adın bütün komponentlərini (kontekst və alt kontekstlər) görə bilərik:
Enumeration<String> elements = objectName.getAll();
while(elements.hasMoreElements()) {
  System.out.println(elements.nextElement());
}
Çıxış aşağıdakı kimi olacaq:

java:comp
env
jdbc
Çıxış göstərir ki, addakı tokenlər bir-birindən slash işarəsi ilə ayrılır (lakin biz bunu qeyd etdik). Hər bir ad nişanının öz indeksi var. Tokenin indeksləşdirilməsi 0-dan başlayır. Kök kontekstdə indeks sıfır, növbəti kontekstdə indeks 1, növbəti 2 və s. Alt kontekst adını indeksinə görə ala bilərik:
System.out.println(objectName.get(1)); // -> env
Əlavə əlamətlər də əlavə edə bilərik (ya sonunda, ya da indeksin müəyyən yerində):
objectName.add("sub-context"); // Добавит sub-context в конец
objectName.add(0, "context"); // Добавит context в налачо
Metodların tam siyahısını rəsmi sənədlərdə tapa bilərsiniz .

İnterfeys konteksti

Bu interfeys konteksti işə salmaq üçün sabitlər dəstini, həmçinin kontekstləri yaratmaq və silmək, obyektləri ada bağlamaq, obyektləri axtarmaq və almaq üçün metodlar toplusunu ehtiva edir. Bu interfeysdən istifadə etməklə həyata keçirilən bəzi əməliyyatlara nəzər salaq. Ən çox görülən əməliyyat obyekti adı ilə axtarmaqdır. Bu üsullardan istifadə etməklə həyata keçirilir:
  • Object lookup(String name)
  • Object lookup(Name name)
Bir obyekti ada bağlamaq üsullardan istifadə etməklə həyata keçirilir bind:
  • void bind(Name name, Object obj)
  • void bind(String name, Object obj)
Hər iki üsul ad adını obyektə bağlayacaq.Obyektin Object addan bağlanmasının tərs əməliyyatı - metodlardan istifadə etməklə həyata keçirilir unbind:
  • void unbind(Name name)
  • void unbind(String name)
Metodların tam siyahısı rəsmi sənədlərin saytında mövcuddur .

İlkin kontekst

InitialContextJNDI ağacının kök elementini təmsil edən və Context. Müəyyən bir qovşağa nisbətən JNDI ağacının içərisində obyektləri adına görə axtarmaq lazımdır. Ağacın kök nodu belə bir düyün kimi xidmət edə bilər InitialContext. JNDI üçün tipik istifadə halı:
  • alın InitialContext.
  • InitialContextJNDI ağacından obyektləri adı ilə almaq üçün istifadə edin .
Onu əldə etməyin bir neçə yolu var InitialContext. Hamısı Java proqramının yerləşdiyi mühitdən asılıdır. Məsələn, Java proqramı və JNDI ağacı eyni proqram serverində işləyirsə, InitialContextəldə etmək olduqca sadədir:
InitialContext context = new InitialContext();
Əgər belə deyilsə, kontekst əldə etmək bir az daha çətinləşir. Bəzən konteksti işə salmaq üçün ətraf mühit xüsusiyyətlərinin siyahısını vermək lazımdır:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
    "com.sun.jndi.fscontext.RefFSContextFactory");

Context ctx = new InitialContext(env);
Yuxarıdakı nümunə konteksti işə salmağın mümkün yollarından birini nümayiş etdirir və heç bir başqa semantik yük daşımır. Koda ətraflı girməyə ehtiyac yoxdur.

SpringBoot vahid testində JNDI-dən istifadə nümunəsi

Yuxarıda dedik ki, JNDI-nin adlandırma və kataloq xidməti ilə qarşılıqlı əlaqədə olması üçün SPI (Xidmət Provayderi İnterfeysi) olmalıdır ki, onun köməyi ilə Java və adlandırma xidməti arasında inteqrasiya həyata keçiriləcək. Standart JDK bir neçə fərqli SPI ilə gəlir (biz onları yuxarıda sadaladıq), hər biri nümayiş məqsədləri üçün az maraq doğurur. JNDI və Java proqramlarını konteynerin içərisində qaldırmaq bir qədər maraqlıdır. Bununla belə, bu məqalənin müəllifi tənbəl insandır, ona görə də JNDI-nin necə işlədiyini nümayiş etdirmək üçün o, ən az müqavimət yolunu seçdi: JNDI-ni SpringBoot proqram vahidi testi daxilində işə salın və Spring Framework-dən kiçik bir hackdən istifadə edərək JNDI kontekstinə daxil olun. Beləliklə, planımız:
  • Gəlin boş bir Spring Boot layihəsi yazaq.
  • Gəlin bu layihə daxilində vahid testi yaradaq.
  • Test daxilində JNDI ilə işləməyi nümayiş etdirəcəyik:
    • kontekstə giriş əldə etmək;
    • JNDI-da hansısa ad altında bəzi obyektləri bağlamaq (bağlamaq);
    • obyekti adı ilə əldə edin (axtarış);
    • Obyektin sıfır olmadığını yoxlayaq.
Sıra ilə başlayaq. File->New->Project... Java-da JNDI-dən istifadə - 3 Sonra, Spring Initializr elementini seçin : Java-da JNDI-dən istifadə - 4Layihə haqqında metadata doldurun: Java-da JNDI-dən istifadə - 5Sonra tələb olunan Spring Framework komponentlərini seçin. Bəzi DataSource obyektlərini bağlayacağıq, ona görə də verilənlər bazası ilə işləmək üçün bizə komponentlər lazımdır:
  • JDBC API;
  • H2 D verilənlər bazası.
Java-da JNDI-dən istifadə - 6Fayl sistemindəki yeri müəyyən edək: Java-da JNDI-dən istifadə - 7Və layihə yaradılır. Əslində, nümayiş məqsədləri üçün istifadə edəcəyimiz bir vahid testi bizim üçün avtomatik olaraq yaradıldı. Aşağıda layihə strukturu və bizə lazım olan test: Java-da JNDI-dən istifadə - 8contextLoads testinin daxilində kod yazmağa başlayaq. Yuxarıda müzakirə edilən Bahardan kiçik bir hack - bu sinifdir SimpleNamingContextBuilder. Bu sinif vahid testləri və ya müstəqil proqramlar daxilində JNDI-ni asanlıqla qaldırmaq üçün nəzərdə tutulmuşdur. Konteksti əldə etmək üçün kodu yazaq:
final SimpleNamingContextBuilder simpleNamingContextBuilder
       = new SimpleNamingContextBuilder();
simpleNamingContextBuilder.activate();

final InitialContext context = new InitialContext();
İlk iki kod sətri bizə JNDI kontekstini daha sonra asanlıqla başlatmağa imkan verəcək. Bunlar olmadan, InitialContextnümunə yaratarkən bir istisna atılacaq: javax.naming.NoInitialContextException. İmtina. Sinif SimpleNamingContextBuilderköhnəlmiş sinifdir. Və bu nümunə JNDI ilə necə işləyə biləcəyinizi göstərmək üçün nəzərdə tutulub. Bunlar JNDI daxili vahid testlərindən istifadə üçün ən yaxşı təcrübələr deyil. Bunun kontekst qurmaq və JNDI-dən obyektləri bağlamaq və əldə etmək üçün bir qoltuqağacı olduğunu söyləmək olar. Kontekst aldıqdan sonra biz ondan obyektlər çıxara və ya kontekstdə obyektləri axtara bilərik. JNDI-də hələ heç bir obyekt yoxdur, ona görə də ora nəsə qoymaq məntiqli olardı. Misal üçün, DriverManagerDataSource:
context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));
Bu sətirdə biz sinif obyektini DriverManagerDataSourceadla bağladıq java:comp/env/jdbc/datasource. Sonra kontekstdən obyekti adla ala bilərik. Sadəcə qoyduğumuz obyekti əldə etməkdən başqa çarəmiz yoxdur, çünki kontekstdə başqa obyekt yoxdur =(
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
İndi DataSource-da əlaqənin olub olmadığını yoxlayaq (bağlantı, əlaqə və ya əlaqə verilənlər bazası ilə işləmək üçün nəzərdə tutulmuş Java sinfidir):
assert ds.getConnection() != null;
System.out.println(ds.getConnection());
Hər şeyi düzgün etdiksə, nəticə belə olacaq:

conn1: url=jdbc:h2:mem:mydb user=
Bəzi kod sətirlərinin istisnalar yarada biləcəyini söyləməyə dəyər. Aşağıdakı sətirlər atılır javax.naming.NamingException:
  • simpleNamingContextBuilder.activate()
  • new InitialContext()
  • context.bind(...)
  • context.lookup(...)
Və bir siniflə işləyərkən DataSourceonu atmaq olar java.sql.SQLException. Bununla əlaqədar olaraq, kodu blok daxilində yerinə yetirmək lazımdır try-catchvə ya test vahidinin imzasında istisnalar ata biləcəyini göstərin. Test sinifinin tam kodu budur:
@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();
        }
    }
}
Testi işə saldıqdan sonra aşağıdakı qeydləri görə bilərsiniz:

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=

Nəticə

Bu gün JNDI-yə baxdıq. Adlandırma və kataloq xidmətlərinin nə olduğunu və JNDI-nin Java proqramından müxtəlif xidmətlərlə vahid şəkildə qarşılıqlı əlaqə yaratmağa imkan verən Java API olduğunu öyrəndik. Məhz, JNDI-nin köməyi ilə biz JNDI ağacında müəyyən bir ad altında obyektləri qeyd edə və eyni obyektləri adla qəbul edə bilərik. Bonus tapşırığı olaraq, JNDI-nin necə işlədiyinə dair bir nümunə işlədə bilərsiniz. Kontekstdə başqa bir obyekti bağlayın və sonra bu obyekti adı ilə oxuyun.
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION