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.
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:- Yüngül Kataloq Giriş Protokolu (LDAP) ;
- CORBA adlandırma xidməti ;
- Şəbəkə İnformasiya Xidməti (NIS) ;
- Və qeyriləri.
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: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:- Nəhayət, Java obyektini əldə etməliyik.
- Bu obyekti bəzi registrdən alacağıq.
- Bu reyestrdə çoxlu obyektlər var.
- Bu reyestrdəki hər bir obyektin özünəməxsus adı var.
- 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”.
- 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).
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).
- javax.naming;
- javax.naming.directory;
- javax.naming.ldap;
- javax.naming.event;
- javax.naming.spi.
İ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)
bind
:
void bind(Name name, Object obj)
void bind(String name, Object obj)
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)
İlkin kontekst
InitialContext
JNDI 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
. InitialContext
JNDI ağacından obyektləri adı ilə almaq üçün istifadə edin .
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.
- JDBC API;
- H2 D verilənlər bazası.
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, InitialContext
nümunə yaratarkən bir istisna atılacaq: javax.naming.NoInitialContextException
. İmtina. Sinif SimpleNamingContextBuilder
kö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 DriverManagerDataSource
adla 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(...)
DataSource
onu atmaq olar java.sql.SQLException
. Bununla əlaqədar olaraq, kodu blok daxilində yerinə yetirmək lazımdır try-catch
və 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=
GO TO FULL VERSION