你好!今天我們將為您介紹JNDI。讓我們了解它是什麼、為什麼需要它、它如何運作以及我們如何使用它。然後我們將編寫一個 Spring Boot 單元測試,在其中我們將使用這個 JNDI。
介紹。命名和目錄服務
在深入研究 JNDI 之前,讓我們先了解什麼是命名和目錄服務。此類服務最明顯的例子是任何 PC、筆記型電腦或智慧型手機上的檔案系統。檔案系統管理(奇怪的是)文件。此類系統中的檔案以樹狀結構分組。每個檔案都有一個唯一的全名,例如:C:\windows\notepad.exe。請注意:完整的檔案名稱是從某個根點(磁碟機 C)到檔案本身(notepad.exe)的路徑。這種鏈中的中間節點就是目錄(windows目錄)。目錄中的檔案具有屬性。例如「隱藏」、「只讀」等。對檔案系統這樣簡單的東西進行詳細的描述,將有助於更好地理解命名和目錄服務的定義。因此,名稱和目錄服務是一個管理多個名稱到多個物件的對應的系統。在我們的檔案系統中,我們與隱藏物件的檔案名稱(各種格式的檔案本身)進行互動。在命名和目錄服務中,命名物件被組織成樹狀結構。且目錄物件具有屬性。名稱和目錄服務的另一個範例是 DNS(網域名稱系統)。該系統管理人類可讀的網域名稱(例如,https://javarush.com/)和機器可讀的IP位址(例如,18.196.51.113)之間的對應。除了 DNS 和檔案系統之外,還有許多其他服務,例如:- 輕量級目錄存取協定(LDAP);
- CORBA 命名服務;
- 網路資訊服務(NIS);
- 和別的。
JNDI
JNDI(Java 命名和目錄介面)是用於存取命名和目錄服務的 Java API。JNDI 是一個 API,它為 Java 程式提供與各種命名和目錄服務互動的統一機制。在底層,JNDI 和任何給定服務之間的整合是使用服務提供者介面 (SPI) 完成的。SPI 允許透明地連接各種命名和目錄服務,從而允許 Java 應用程式使用 JNDI API 存取連接的服務。下圖展示了JNDI架構:JNDI。用簡單的話來說意思
主要問題是:為什麼我們需要 JNDI?需要 JNDI,以便我們可以透過綁定到該物件的物件名稱從 Java 程式碼中的物件「註冊」中取得 Java 物件。讓我們將上面的陳述分成幾個主題,這樣大量重複的單字就不會讓我們感到困惑:- 最終我們需要得到一個Java物件。
- 我們將從某個註冊表中取得該物件。
- 這個註冊表中有很多物件。
- 該註冊表中的每個物件都有一個唯一的名稱。
- 要從註冊表取得對象,我們必須在請求中傳遞一個名稱。彷彿在說:“請把你在某某名字下的東西給我。”
- 我們不僅可以透過註冊表中的名稱讀取對象,還可以將對像以某些名稱保存在註冊表中(以某種方式它們最終會出現在那裡)。
JNDI API
JNDI 在 Java SE 平台內提供。若要使用 JNDI,您必須匯入 JNDI 類別以及一個或多個服務提供者以存取命名和目錄服務。JDK 包含以下服務的服務提供者:- 輕量級目錄存取協定(LDAP);
- 通用物件請求代理架構(CORBA);
- 通用物件服務(COS)名稱服務;
- Java 遠端方法呼叫 (RMI) 註冊表;
- 域名服務 (DNS)。
- javax.命名;
- javax.命名.目錄;
- javax.naming.ldap;
- javax.命名.事件;
- javax.naming.spi。
介面名稱
Name 介面可讓您控制元件名稱以及 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
是一個類,表示 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);
上面的範例演示了初始化上下文的可能方法之一,並且不攜帶任何其他語義負載。無需深入研究程式碼的細節。
在 SpringBoot 單元測試中使用 JNDI 的範例
上面我們說過,JNDI要與命名和目錄服務進行交互,就需要有SPI(Service Provider Interface),借助它來進行Java與命名服務的整合。標準 JDK 附帶了幾個不同的 SPI(我們在上面列出了它們),其中每一個對於演示目的來說都沒什麼意義。在容器內產生 JNDI 和 Java 應用程式有些有趣。然而,本文的作者是個懶人,因此為了演示 JNDI 的工作原理,他選擇了阻力最小的路徑:在 SpringBoot 應用程式單元測試中執行 JNDI,並使用 Spring 框架中的一個小技巧來存取 JNDI 上下文。所以,我們的計劃:- 讓我們來寫一個空的 Spring Boot 專案。
- 讓我們在這個專案中建立一個單元測試。
- 在測試中,我們將示範如何使用 JNDI:
- 訪問上下文;
- 在 JNDI 中綁定(bind)某個名稱下的某個物件;
- 透過名稱獲取物件(查找);
- 讓我們檢查該物件是否不為空。
- 資料庫介面;
- H2 D資料庫。
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=
GO TO FULL VERSION