سلام! امروز شما را با JNDI آشنا می کنیم. بیایید دریابیم که چیست، چرا به آن نیاز است، چگونه کار می کند، چگونه می توانیم با آن کار کنیم. و سپس یک تست واحد Spring Boot می نویسیم که در داخل آن با همین JNDI بازی می کنیم.
معرفی. خدمات نامگذاری و دایرکتوری
قبل از پرداختن به JNDI، بیایید بفهمیم که خدمات نامگذاری و دایرکتوری چیست. بارزترین مثال از چنین سرویسی، سیستم فایل در هر رایانه شخصی، لپ تاپ یا گوشی هوشمند است. سیستم فایل فایل ها را (به اندازه کافی عجیب) مدیریت می کند. فایل ها در چنین سیستم هایی در یک ساختار درختی گروه بندی می شوند. هر فایل یک نام کامل منحصر به فرد دارد، به عنوان مثال: C:\windows\notepad.exe. لطفاً توجه داشته باشید: نام کامل فایل مسیری است از یک نقطه ریشه (درایو C) به خود فایل (notepad.exe). گره های میانی در چنین زنجیره ای دایرکتوری ها (دایرکتوری ویندوز) هستند. فایل های داخل دایرکتوری ها دارای ویژگی هایی هستند. به عنوان مثال، "Hidden"، "Read-Only"، و غیره. شرح دقیق یک چیز ساده مانند یک سیستم فایل به درک بهتر تعریف خدمات نامگذاری و فهرست کمک می کند. بنابراین، یک سرویس نام و دایرکتوری سیستمی است که نگاشت بسیاری از نام ها به بسیاری از اشیاء را مدیریت می کند. در سیستم فایل ما، ما با نام فایلهایی که اشیاء را پنهان میکنند، تعامل داریم - خود فایلها در قالبهای مختلف. در سرویس نامگذاری و فهرست، اشیاء نامگذاری شده در یک ساختار درختی سازماندهی می شوند. و اشیاء دایرکتوری دارای ویژگی هستند. نمونه دیگری از خدمات نام و فهرست، DNS (سیستم نام دامنه) است. این سیستم نقشه برداری بین نام دامنه های قابل خواندن توسط انسان (به عنوان مثال، https://javarush.com/) و آدرس های IP قابل خواندن توسط ماشین (به عنوان مثال، 18.196.51.113) را مدیریت می کند. علاوه بر DNS و فایل سیستمها، خدمات دیگری نیز وجود دارد، مانند:JNDI
JNDI یا Java Naming and Directory Interface یک API جاوا برای دسترسی به خدمات نامگذاری و دایرکتوری است. JNDI یک API است که مکانیسم یکنواختی را برای یک برنامه جاوا برای تعامل با سرویسهای نامگذاری و دایرکتوری مختلف فراهم میکند. در زیر هود، ادغام بین JNDI و هر سرویس داده شده با استفاده از یک رابط ارائه دهنده خدمات (SPI) انجام می شود. SPI اجازه می دهد تا سرویس های مختلف نامگذاری و دایرکتوری به طور شفاف متصل شوند و به یک برنامه جاوا اجازه می دهد تا از JNDI API برای دسترسی به خدمات متصل استفاده کند. شکل زیر معماری JNDI را نشان می دهد:منبع: Oracle Java Tutorials
JNDI. معنی در کلمات ساده
سوال اصلی این است: چرا به JNDI نیاز داریم؟ JNDI مورد نیاز است تا بتوانیم یک شی جاوا را از "ثبت" اشیاء از کد جاوا با نام شی محدود شده به این شیء دریافت کنیم. بیایید بیانیه بالا را به تزها تقسیم کنیم تا فراوانی کلمات تکراری ما را گیج نکند:- در نهایت باید یک شی جاوا بدست آوریم.
- ما این شی را از برخی از رجیستری دریافت خواهیم کرد.
- دسته ای از اشیاء در این رجیستری وجود دارد.
- هر شیء در این رجیستری یک نام منحصر به فرد دارد.
- برای دریافت یک شی از رجیستری، باید یک نام را در درخواست خود وارد کنیم. انگار میگوید: «لطفاً آنچه را که داری به فلان اسم به من بده.»
- ما نه تنها میتوانیم اشیاء را با نام آنها از رجیستری بخوانیم، بلکه میتوانیم اشیاء را در این رجیستری با نامهای خاصی ذخیره کنیم (به نحوی که به آنجا ختم میشوند).
JNDI API
JNDI در بستر Java SE ارائه شده است. برای استفاده از JNDI، باید کلاسهای JNDI و همچنین یک یا چند ارائهدهنده خدمات را وارد کنید تا به خدمات نامگذاری و دایرکتوری دسترسی داشته باشید. JDK شامل ارائه دهندگان خدمات برای خدمات زیر است:- پروتکل دسترسی به دایرکتوری سبک (LDAP)؛
- معماری کارگزار درخواست شی مشترک (CORBA);
- خدمات نام مشترک شیء (COS)؛
- رجیستری فراخوانی روش راه دور جاوا (RMI).
- سرویس نام دامنه (DNS).
- javax.name;
- javax.name.directory;
- javax.name.ldap;
- javax.name.event;
- javax.name.spi.
نام رابط
رابط Name به شما امکان می دهد نام اجزا و همچنین نحو نامگذاری JNDI را کنترل کنید. در JNDI، تمام عملیات نام و دایرکتوری نسبت به زمینه انجام می شود. هیچ ریشه مطلقی وجود ندارد. بنابراین، JNDI یک InitialContext را تعریف می کند، که نقطه شروعی را برای عملیات نامگذاری و دایرکتوری فراهم می کند. پس از دسترسی به بافت اولیه، می توان از آن برای جستجوی اشیا و سایر زمینه ها استفاده کرد.Name objectName = new CompositeName("java:comp/env/jdbc");
در کد بالا، نامی را تعریف کردیم که تحت آن یک شی قرار دارد (ممکن است قرار نداشته باشد، اما روی آن حساب می کنیم). هدف نهایی ما به دست آوردن یک مرجع برای این شی و استفاده از آن در برنامه است. بنابراین، نام شامل چندین بخش (یا نشانه) است که با یک اسلش از هم جدا شده اند. چنین نشانه هایی را زمینه می نامند. اولین مورد صرفاً زمینه است، همه موارد بعدی زیر زمینه هستند (از این پس به عنوان زیر زمینه نامیده می شود). درک زمینهها آسانتر است اگر آنها را مشابه فهرستها یا فهرستها یا فقط پوشههای معمولی در نظر بگیرید. زمینه ریشه، پوشه ریشه است. Subcontext یک زیرپوشه است. با اجرای کد زیر میتوانیم تمام مؤلفهها (زمینه و زمینههای فرعی) یک نام معین را ببینیم:
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
. همه چیز بستگی به محیطی دارد که برنامه جاوا در آن قرار دارد. به عنوان مثال، اگر یک برنامه جاوا و یک درخت 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 (رابط ارائه دهنده سرویس) است که به کمک آن یکپارچگی بین جاوا و سرویس نامگذاری انجام می شود. JDK استاندارد با چندین SPI مختلف (ما آنها را در بالا لیست کردیم) ارائه می شود که هر کدام برای اهداف نمایشی جالب توجه نیستند. بالا بردن یک برنامه JNDI و جاوا در داخل یک کانتینر تا حدودی جالب است. با این حال، نویسنده این مقاله یک فرد تنبل است، بنابراین برای نشان دادن نحوه عملکرد JNDI، او مسیر کمترین مقاومت را انتخاب کرد: JNDI را در یک تست واحد برنامه کاربردی SpringBoot اجرا کنید و با استفاده از یک هک کوچک از Spring Framework به زمینه JNDI دسترسی پیدا کنید. بنابراین، طرح ما:- بیایید یک پروژه خالی بهار بوت بنویسیم.
- بیایید یک تست واحد در داخل این پروژه ایجاد کنیم.
- در داخل آزمون کار با JNDI را نشان خواهیم داد:
- دسترسی به زمینه؛
- اتصال (پیوند) برخی از شی ها تحت نامی در JNDI.
- دریافت شی با نام آن (جستجو)؛
- بیایید بررسی کنیم که شی تهی نباشد.
- JDBC API;
- H2 DDatabase.
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
در این خط، شی کلاس را به name متصل کرده ایم java:comp/env/jdbc/datasource
. در مرحله بعد، میتوانیم شی را با نام از متن دریافت کنیم. ما چاره ای نداریم جز اینکه شیئی را که قرار داده ایم بدست آوریم، زیرا هیچ شیء دیگری در متن وجود ندارد =(
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
حالا بیایید بررسی کنیم که DataSource ما یک اتصال دارد (اتصال، اتصال یا اتصال یک کلاس جاوا است که برای کار با پایگاه داده طراحی شده است):
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=
GO TO FULL VERSION