JavaRush /Java Blog /Random-TW /在 Java 中使用 JNDI
Анзор Кармов
等級 31
Санкт-Петербург

在 Java 中使用 JNDI

在 Random-TW 群組發布
你好!今天我們將為您介紹JNDI。讓我們了解它是什麼、為什麼需要它、它如何運作以及我們如何使用它。然後我們將編寫一個 Spring Boot 單元測試,在其中我們將使用這個 JNDI。 在 Java 中使用 JNDI - 1

介紹。命名和目錄服務

在深入研究 JNDI 之前,讓我們先了解什麼是命名和目錄服務。此類服務最明顯的例子是任何 PC、筆記型電腦或智慧型手機上的檔案系統。檔案系統管理(奇怪的是)文件。此類系統中的檔案以樹狀結構分組。每個檔案都有一個唯一的全名,例如:C:\windows\notepad.exe。請注意:完整的檔案名稱是從某個根點(磁碟機 C)到檔案本身(notepad.exe)的路徑。這種鏈中的中間節點就是目錄(windows目錄)。目錄中的檔案具有屬性。例如「隱藏」、「只讀」等。對檔案系統這樣簡單的東西進行詳細的描述,將有助於更好地理解命名和目錄服務的定義。因此,名稱和目錄服務是一個管理多個名稱到多個物件的對應的系統。在我們的檔案系統中,我們與隱藏物件的檔案名稱(各種格式的檔案本身)進行互動。在命名和目錄服務中,命名物件被組織成樹狀結構。且目錄物件具有屬性。名稱和目錄服務的另一個範例是 DNS(網域名稱系統)。該系統管理人類可讀的網域名稱(例如,https://javarush.com/)和機器可讀的IP位址(例如,18.196.51.113)之間的對應。除了 DNS 和檔案系統之外,還有許多其他服務,例如:

JNDI

JNDI(Java 命名和目錄介面)是用於存取命名和目錄服務的 Java API。JNDI 是一個 API,它為 Java 程式提供與各種命名和目錄服務互動的統一機制。在底層,JNDI 和任何給定服務之間的整合是使用服務提供者介面 (SPI) 完成的。SPI 允許透明地連接各種命名和目錄服務,從而允許 Java 應用程式使用 JNDI API 存取連接的服務。下圖展示了JNDI架構: 在 Java 中使用 JNDI - 2

來源:Oracle Java 教學課程

JNDI。用簡單的話來說意思

主要問題是:為什麼我們需要 JNDI?需要 JNDI,以便我們可以透過綁定到該物件的物件名稱從 Java 程式碼中的物件「註冊」中取得 Java 物件。讓我們將上面的陳述分成幾個主題,這樣大量重複的單字就不會讓我們感到困惑:
  1. 最終我們需要得到一個Java物件。
  2. 我們將從某個註冊表中取得該物件。
  3. 這個註冊表中有很多物件。
  4. 該註冊表中的每個物件都有一個唯一的名稱。
  5. 要從註冊表取得對象,我們必須在請求中傳遞一個名稱。彷彿在說:“請把你在某某名字下的東西給我。”
  6. 我們不僅可以透過註冊表中的名稱讀取對象,還可以將對像以某些名稱保存在註冊表中(以某種方式它們最終會出現在那裡)。
因此,我們有某種註冊表、物件儲存或 JNDI 樹。接下來,我們透過一個例子來嘗試理解JNDI的含義。值得注意的是,JNDI 大部分用於企業開發。此類應用程式在某些應用程式伺服器內運行。該伺服器可以是某個 Java EE 應用程式伺服器,或像 Tomcat 這樣的 servlet 容器,或任何其他容器。物件註冊表本身,即 JNDI 樹,通常位於該應用程式伺服器內部。後者並不總是必要的(您可以在本地擁有這樣一棵樹),但它是最典型的。JNDI 樹可以由特殊人員(系統管理員或 DevOps 專家)管理,他們將使用名稱「儲存在登錄中」物件。當我們的應用程式和 JNDI 樹位於同一個容器內時,我們可以輕鬆存取儲存在此類註冊表中的任何 Java 物件。而且,註冊表和我們的應用程式可以位於不同的容器中,甚至位於不同的實體機器上。JNDI 甚至允許您遠端存取 Java 物件。典型。Java EE 伺服器管理員在登錄中放置一個對象,該對象儲存連接到資料庫所需的資訊。因此,要使用資料庫,我們只需從 JNDI 樹請求所需的物件並使用它即可。非常舒服。方便還在於企業發展有不同的環境。有生產伺服器,也有測試伺服器(通常不只 1 台測試伺服器)。然後,透過在JNDI 內的每台伺服器上放置一個用於連接資料庫的對象,並在我們的應用程式中使用該對象,在將應用程式從一台伺服器(測試、發布)部署到另一台伺服器時,我們無需進行任何更改。到處都可以存取資料庫。當然,這個例子有些簡化,但我希望它能幫助您更好地理解為什麼需要 JNDI。接下來,我們將透過一些攻擊元素更深入地了解 Java 中的 JNDI。

JNDI API

JNDI 在 Java SE 平台內提供。若要使用 JNDI,您必須匯入 JNDI 類別以及一個或多個服務提供者以存取命名和目錄服務。JDK 包含以下服務的服務提供者:
  • 輕量級目錄存取協定(LDAP);
  • 通用物件請求代理架構(CORBA);
  • 通用物件服務(COS)名稱服務;
  • Java 遠端方法呼叫 (RMI) 註冊表;
  • 域名服務 (DNS)。
JNDI API程式碼分為幾個包:
  • javax.命名;
  • javax.命名.目錄;
  • javax.naming.ldap;
  • javax.命名.事件;
  • javax.naming.spi。
我們將從兩個介面開始介紹 JNDI - Name 和 Context,它們包含關鍵的 JNDI 功能

介面名稱

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)
兩種方法都會將名稱 name 綁定到物件。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)某個名稱下的某個物件;
    • 透過名稱獲取物件(查找);
    • 讓我們檢查該物件是否不為空。
讓我們按順序開始。 File->New->Project... 在 Java 中使用 JNDI - 3接下來,選擇Spring Initializr項目: 在 Java 中使用 JNDI - 4填入有關項目的元資料: 在 Java 中使用 JNDI - 5然後選擇所需的 Spring Framework 元件。我們將綁定一些 DataSource 對象,因此我們需要元件來處理資料庫:
  • 資料庫介面;
  • H2 D資料庫。
在 Java 中使用 JNDI - 6讓我們確定檔案系統中的位置: 在 Java 中使用 JNDI - 7項目已建立。事實上,系統會自動為我們產生一個單元測試,我們將使用它來進行演示。以下是專案結構和我們需要的測試: 在 Java 中使用 JNDI - 8讓我們開始在 contextLoads 測試中編寫程式碼。上面討論的 Spring 的一個小技巧是類別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=

結論

今天我們研究了 JNDI。我們了解了命名和目錄服務是什麼,以及 JNDI 是 Java API,它允許您透過 Java 程式與不同的服務進行統一互動。也就是說,借助JNDI,我們可以將物件以某個名稱記錄在JNDI樹中,並透過名稱接收這些相同的物件。作為獎勵任務,您可以執行 JNDI 如何運作的範例。將一些其他物件綁定到上下文中,然後按名稱讀取該物件。
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION