Halo! Hari ini kami akan memperkenalkan Anda kepada JNDI. Mari kita cari tahu apa itu, mengapa dibutuhkan, cara kerjanya, bagaimana kita bisa mengatasinya. Dan kemudian kita akan menulis tes unit Spring Boot, di dalamnya kita akan bermain dengan JNDI ini.
Perkenalan. Layanan Penamaan dan Direktori
Sebelum mendalami JNDI, mari kita pahami apa itu layanan penamaan dan direktori. Contoh paling nyata dari layanan semacam itu adalah sistem file di PC, laptop, atau ponsel cerdas mana pun. Sistem file mengelola (anehnya) file. File dalam sistem tersebut dikelompokkan dalam struktur pohon. Setiap file memiliki nama lengkap yang unik, misalnya: C:\windows\notepad.exe. Harap diperhatikan: nama lengkap file adalah jalur dari beberapa titik root (drive C) ke file itu sendiri (notepad.exe). Node perantara dalam rantai tersebut adalah direktori (direktori windows). File di dalam direktori memiliki atribut. Misalnya, “Tersembunyi”, “Hanya-Baca”, dll. Penjelasan rinci tentang hal sederhana seperti sistem file akan membantu untuk lebih memahami definisi penamaan dan layanan direktori. Jadi, layanan nama dan direktori adalah sistem yang mengelola pemetaan banyak nama ke banyak objek. Dalam sistem file, kita berinteraksi dengan nama file yang menyembunyikan objek—file itu sendiri dalam berbagai format. Dalam layanan penamaan dan direktori, objek yang diberi nama disusun ke dalam struktur pohon. Dan objek direktori memiliki atribut. Contoh lain dari layanan nama dan direktori adalah DNS (Domain Name System). Sistem ini mengelola pemetaan antara nama domain yang dapat dibaca manusia (misalnya https://javarush.com/) dan alamat IP yang dapat dibaca mesin (misalnya 18.196.51.113). Selain DNS dan sistem file, masih banyak layanan lainnya, seperti:- Protokol Akses Direktori Ringan (LDAP) ;
- layanan penamaan CORBA ;
- Layanan Informasi Jaringan (NIS) ;
- Dan lain-lain.
JNDI
JNDI, atau Java Naming and Directory Interface, adalah Java API untuk mengakses layanan penamaan dan direktori. JNDI adalah API yang menyediakan mekanisme seragam bagi program Java untuk berinteraksi dengan berbagai layanan penamaan dan direktori. Integrasi antara JNDI dan layanan apa pun dilakukan menggunakan Antarmuka Penyedia Layanan (SPI). SPI memungkinkan berbagai layanan penamaan dan direktori terhubung secara transparan, memungkinkan aplikasi Java menggunakan API JNDI untuk mengakses layanan yang terhubung. Gambar di bawah mengilustrasikan arsitektur JNDI:Sumber: Tutorial Oracle Java
JNDI. Artinya dengan kata-kata sederhana
Pertanyaan utamanya adalah: mengapa kita membutuhkan JNDI? JNDI diperlukan agar kita bisa mendapatkan objek Java dari beberapa “Registrasi” objek dari kode Java dengan nama objek yang terikat pada objek tersebut. Mari kita pecahkan pernyataan di atas menjadi beberapa hal ini agar banyaknya kata yang diulang-ulang tidak membingungkan kita:- Pada akhirnya kita perlu mendapatkan objek Java.
- Kami akan mendapatkan objek ini dari beberapa registri.
- Ada banyak objek di registri ini.
- Setiap objek dalam registri ini memiliki nama unik.
- Untuk mendapatkan objek dari registri, kita harus memberikan nama dalam permintaan kita. Seolah-olah mengatakan: “Tolong beri saya apa yang Anda miliki dengan nama ini dan itu.”
- Kita tidak hanya dapat membaca objek berdasarkan namanya dari registri, tetapi juga menyimpan objek dalam registri ini dengan nama tertentu (entah bagaimana objek tersebut berakhir di sana).
API JNDI
JNDI disediakan dalam platform Java SE. Untuk menggunakan JNDI, Anda harus mengimpor kelas JNDI, serta satu atau beberapa penyedia layanan untuk mengakses layanan penamaan dan direktori. JDK mencakup penyedia layanan untuk layanan berikut:- Protokol Akses Direktori Ringan (LDAP);
- Arsitektur Broker Permintaan Objek Umum (CORBA);
- layanan nama Common Object Services (COS);
- Registri Invokasi Metode Jarak Jauh Java (RMI);
- Layanan Nama Domain (DNS).
- javax.penamaan;
- javax.penamaan.direktori;
- javax.penamaan.ldap;
- javax.penamaan.acara;
- javax.penamaan.spi.
Nama Antarmuka
Antarmuka Nama memungkinkan Anda mengontrol nama komponen serta sintaks penamaan JNDI. Di JNDI, semua operasi nama dan direktori dilakukan sesuai dengan konteksnya. Tidak ada akar yang mutlak. Oleh karena itu, JNDI mendefinisikan InitialContext, yang menyediakan titik awal untuk operasi penamaan dan direktori. Setelah konteks awal diakses, konteks tersebut dapat digunakan untuk mencari objek dan konteks lainnya.Name objectName = new CompositeName("java:comp/env/jdbc");
Dalam kode di atas, kami mendefinisikan beberapa nama di mana suatu objek berada (mungkin tidak berlokasi, tapi kami mengandalkannya). Tujuan akhir kami adalah mendapatkan referensi ke objek ini dan menggunakannya dalam program kami. Jadi, namanya terdiri dari beberapa bagian (atau token), dipisahkan dengan garis miring. Token seperti ini disebut konteks. Yang pertama hanyalah konteks, semua yang berikutnya adalah subkonteks (selanjutnya disebut subkonteks). Konteks lebih mudah dipahami jika Anda menganggapnya sebagai analogi direktori atau direktori, atau sekadar folder biasa. Konteks root adalah folder root. Subkonteks adalah subfolder. Kita dapat melihat semua komponen (konteks dan subkonteks) dari nama tertentu dengan menjalankan kode berikut:
Enumeration<String> elements = objectName.getAll();
while(elements.hasMoreElements()) {
System.out.println(elements.nextElement());
}
Outputnya adalah sebagai berikut:
java:comp
env
jdbc
Outputnya menunjukkan bahwa token dalam nama dipisahkan satu sama lain dengan garis miring (namun, kami menyebutkan ini). Setiap token nama memiliki indeksnya sendiri. Pengindeksan token dimulai dari 0. Konteks akar memiliki indeks nol, konteks berikutnya memiliki indeks 1, berikutnya 2, dan seterusnya. Kita bisa mendapatkan nama subkonteks berdasarkan indeksnya:
System.out.println(objectName.get(1)); // -> env
Kita juga dapat menambahkan token tambahan (baik di akhir atau di lokasi tertentu dalam indeks):
objectName.add("sub-context"); // Добавит sub-context в конец
objectName.add(0, "context"); // Добавит context в налачо
Daftar lengkap metode dapat ditemukan di dokumentasi resmi .
Konteks Antarmuka
Antarmuka ini berisi sekumpulan konstanta untuk menginisialisasi konteks, serta sekumpulan metode untuk membuat dan menghapus konteks, mengikat objek ke sebuah nama, dan mencari serta mengambil objek. Mari kita lihat beberapa operasi yang dilakukan menggunakan antarmuka ini. Tindakan paling umum adalah mencari objek berdasarkan nama. Ini dilakukan dengan menggunakan metode:Object lookup(String name)
Object lookup(Name name)
bind
:
void bind(Name name, Object obj)
void bind(String name, Object obj)
Object
kebalikan dari pengikatan - melepaskan ikatan objek dari sebuah nama, dilakukan dengan menggunakan metode unbind
:
void unbind(Name name)
void unbind(String name)
Konteks Awal
InitialContext
adalah kelas yang mewakili elemen akar pohon JNDI dan mengimplementasikan file Context
. Anda perlu mencari objek berdasarkan nama di dalam pohon JNDI yang berhubungan dengan node tertentu. Node akar pohon dapat berfungsi sebagai node tersebut InitialContext
. Kasus penggunaan umum untuk JNDI adalah:
- Mendapatkan
InitialContext
. - Gunakan
InitialContext
untuk mengambil objek berdasarkan nama dari pohon JNDI.
InitialContext
. Itu semua tergantung pada lingkungan di mana program Java berada. Misalnya, jika program Java dan pohon JNDI berjalan di dalam server aplikasi yang sama, maka cukup InitialContext
mudah untuk mendapatkannya:
InitialContext context = new InitialContext();
Jika hal ini tidak terjadi, mendapatkan konteks menjadi sedikit lebih sulit. Terkadang perlu meneruskan daftar properti lingkungan untuk menginisialisasi konteks:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
Context ctx = new InitialContext(env);
Contoh di atas menunjukkan salah satu cara yang mungkin untuk menginisialisasi konteks dan tidak membawa muatan semantik lainnya. Tidak perlu mendalami kode secara detail.
Contoh penggunaan JNDI di dalam pengujian unit SpringBoot
Di atas kami katakan bahwa agar JNDI dapat berinteraksi dengan layanan penamaan dan direktori, diperlukan SPI (Antarmuka Penyedia Layanan), yang dengannya integrasi antara Java dan layanan penamaan akan dilakukan. JDK standar hadir dengan beberapa SPI berbeda (kami mencantumkannya di atas), yang masing-masing kurang menarik untuk tujuan demonstrasi. Memunculkan aplikasi JNDI dan Java di dalam sebuah container terbilang menarik. Namun, penulis artikel ini adalah orang yang malas, jadi untuk mendemonstrasikan cara kerja JNDI, ia memilih jalur yang paling sedikit hambatannya: jalankan JNDI di dalam pengujian unit aplikasi SpringBoot dan akses konteks JNDI menggunakan peretasan kecil dari Spring Framework. Jadi, rencana kami:- Mari kita menulis proyek Spring Boot yang kosong.
- Mari buat pengujian unit di dalam proyek ini.
- Di dalam tes ini kami akan mendemonstrasikan cara bekerja dengan JNDI:
- dapatkan akses ke konteksnya;
- mengikat (mengikat) beberapa objek dengan nama tertentu di JNDI;
- dapatkan objek berdasarkan namanya (pencarian);
- Mari kita periksa apakah objeknya bukan null.
- API JDBC;
- Basis Data H2.
SimpleNamingContextBuilder
. Kelas ini dirancang untuk dengan mudah meningkatkan JNDI di dalam pengujian unit atau aplikasi yang berdiri sendiri. Mari tulis kode untuk mendapatkan konteksnya:
final SimpleNamingContextBuilder simpleNamingContextBuilder
= new SimpleNamingContextBuilder();
simpleNamingContextBuilder.activate();
final InitialContext context = new InitialContext();
Dua baris kode pertama akan memungkinkan kita menginisialisasi konteks JNDI dengan mudah nanti. Tanpa mereka, InitialContext
pengecualian akan muncul saat membuat sebuah instance: javax.naming.NoInitialContextException
. Penafian. Kelas tersebut SimpleNamingContextBuilder
adalah kelas yang tidak digunakan lagi. Dan contoh ini dimaksudkan untuk menunjukkan bagaimana Anda dapat bekerja dengan JNDI. Ini bukan praktik terbaik untuk menggunakan JNDI di dalam pengujian unit. Hal ini dapat dikatakan sebagai penopang untuk membangun konteks dan mendemonstrasikan pengikatan dan pengambilan objek dari JNDI. Setelah menerima konteks, kita dapat mengekstrak objek darinya atau mencari objek dalam konteks tersebut. Belum ada objek di JNDI, jadi logis untuk meletakkan sesuatu di sana. Misalnya, DriverManagerDataSource
:
context.bind("java:comp/env/jdbc/datasource", new DriverManagerDataSource("jdbc:h2:mem:mydb"));
Pada baris ini, kita telah mengikat objek kelas DriverManagerDataSource
ke nama java:comp/env/jdbc/datasource
. Selanjutnya, kita bisa mendapatkan objek dari konteksnya berdasarkan namanya. Kita tidak punya pilihan selain mendapatkan objek yang baru saja kita masukkan, karena tidak ada objek lain dalam konteksnya =(
final DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/datasource");
Sekarang mari kita periksa apakah DataSource kita memiliki koneksi (koneksi, koneksi, atau koneksi adalah kelas Java yang dirancang untuk bekerja dengan database):
assert ds.getConnection() != null;
System.out.println(ds.getConnection());
Jika kita melakukan semuanya dengan benar, hasilnya akan seperti ini:
conn1: url=jdbc:h2:mem:mydb user=
Perlu dikatakan bahwa beberapa baris kode mungkin menimbulkan pengecualian. Baris berikut dilemparkan javax.naming.NamingException
:
simpleNamingContextBuilder.activate()
new InitialContext()
context.bind(...)
context.lookup(...)
DataSource
itu bisa dilempar java.sql.SQLException
. Dalam hal ini, perlu untuk mengeksekusi kode di dalam blok try-catch
, atau menunjukkan dalam tanda tangan unit pengujian bahwa ia dapat mengeluarkan pengecualian. Berikut ini kode lengkap kelas tes:
@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();
}
}
}
Setelah menjalankan pengujian, Anda dapat melihat log berikut:
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