Java命名和目錄接口(Java Naming and Directory Interface ,JNDI)是用於從Java應用程序中訪問名稱和目錄服務的一組API。命名服務即將名稱與對象相關聯,以便能通過相應名稱訪問這些對象。而目錄服務即其對象具有屬性及名稱的命名服務。
命名或目錄服務允許您集中管理共享信息的存儲,這在網絡應用程序中很重要,因爲它可以使這類應用程序更加一致和易於管理。例如,可以將打印機配置存儲在目錄服務中,這樣所有與打印機相關的應用程序都能夠使用它。
本文是一份代碼密集型的快速入門指南,讓您開始瞭解和使用JNDI。它:
- 提供對JNDI的綜述。
- 描述JNDI的特性。
- 提供使用JNDI開發應用程序過程中的體驗。
- 說明如何使用JNDI訪問 LDAP 服務器,比如Sun ONE Directory Server 。
- 說明如何使用 JNDI 訪問J2EE 服務。
- 提供示例代碼,您可以對其進行修改,以用於您自己的應用程序。
JNDI綜述
我們所有人每天都在不自知的情況下使用命名服務。例如,當您在瀏覽器中輸入URL http://java.sun.com 時,域名系統(Domain Name System ,DNS)將這個以符號表示的URL轉換爲一個通信標識符(IP地址)。在命名系統中,對象的範圍可以從位於DNS記錄中的名稱變動到應用程序服務器中的企業JavaBeans組件(Enterprise JavaBeans Components ,EJBs),還可以到輕量級目錄訪問協議(Lightweight Directory Access Protocol ,LDAP)中的用戶配置文件。
目錄服務是命名服務的自然擴展。二者的關鍵區別在於,目錄服務允許屬性(比如用戶的電子郵件地址)與對象相關聯,而命名服務則不然。這樣,使用目錄服務時,您可以基於對象的屬性來搜索它們。JNDI允許您訪問文件系統中的文件,定位遠程RMI註冊表中的對象,訪問諸如LDAP這樣的目錄服務,並定位網絡上的EJB。
很多應用程序選擇使用JNDI都可以收到良好的效果,比如LDAP客戶端、應用程序啓動器、類瀏覽器、網絡管理實用工具,或者甚至是地址簿。
JNDI架構
JNDI架構提供了一個標準的、與命名系統無關的API,這個API構建在特定於命名系統的驅動程序之上。這一層幫助把應用程序和實際的數據源隔離開來,因此無論應用程序是訪問LDAP、RMI、DNS還是其他的目錄服務,這都沒有關係。換句話說,JNDI與任何特定的目錄服務實現無關,您可以使用任何目錄,只要您擁有相應的服務提供程序接口(或驅動程序)即可,如圖1所示。
圖1: JNDI架構
注意,關於JNDI有一點很重要,即它同時提供應用程序編程接口(Application Programming Interface ,API)和服務提供程序接口(Service Provider Interface ,SPI)。這樣做的實際意義在於,對於您的與命名或目錄服務交互的應用程序來說,必須存在用於該服務的一個JNDI服務提供程序,這便是JNDI SPI發揮作用的舞臺。一個服務提供程序基本上就是一組類,這些類針對特定的命名和目錄服務實現了各種JNDI接口——這與JDBC驅動程序針對特定的數據系統實現各種JDBC接口極爲相似。作爲一名應用程序開發人員,您不需要擔心JNDI SPI.。您只需確保,您爲每個想使用的命名或目錄服務提供了一個服務提供程序。
J2SE和JNDI
JNDI被包含在Java 2 SDK 1.3 及其更新版本中。它還可以用作JDK 1.1和1.2的一個標準擴展。 Java 2 SDK 1.4.x的最新版本進行了改進,將以下命名/目錄服務提供程序包括進來:
- 輕量級目錄訪問協議(Lightweight Directory Access Protocol,LDAP) 服務提供程序。
- 公共對象請求代理架構(Common Object Request Broker Architecture ,CORBA)公共對象服務(Common Object Services ,COS)命名服務提供程序。
- Java遠程方法調用( Remote Method Invocation ,RMI)註冊表服務提供程序。
- 域名系統( Domain Name System ,DNS) 服務提供程序。
有關服務提供程序的更多內容
在這裏可以下載一系列服務提供程序。Windows註冊表JNDI 提供程序(來自cogentlogic.com)可能會引起您特別的興趣,因爲它允許您訪問Windows XP/2000/NT/Me/9x上的註冊表。
此外,還可以下載JNDI/LDAP Bootster Pack。這個增強補丁包含對流行的LDAP控件和擴展的支持。它代替了與LDAP 1.2.1服務提供程序捆綁在一起的增強補丁。參見 Controls and Extensions 以獲得更多信息。
另一個要考察的有趣的服務提供程序是Sun的Directory Services Markup Language (DSML) v2.0提供程序。 DSML的目標是將目錄服務與XML連接起來
JNDI API
JNDI API 包括5個包:
- javax.naming: 包含用於訪問命名服務的類和接口。例如,它定義了Context接口,該接口是執行查找時命名服務的入口點。
- javax.naming.directory:擴展命名包以提供用於訪問目錄服務的類和接口。例如,它增加了新的屬性類,提供代表一個目錄上下文的DirContext 接口,並且定義了用於檢查和更新與目錄對象相關的屬性的方法。
- javax.naming.event: 當訪問命名和目錄服務時,爲事件通知提供支持。例如,它定義了一個NamingEvent類,用於表示由命名/目錄服務生成的事件,以及一個監視NamingEvents 類的, NamingListener 接口。
- javax.naming.ldap: 這個包爲LDAP 版本 3 擴展操作和空間提供特定的支持,而普通的javax.naming.directory 包沒有提供這些支持。
- javax.naming.spi: 這個包提供方法以動態插入對通過javax.naming及其相關包訪問命名和目錄服務的支持。只有那些對創建服務提供程序有着濃厚興趣的開發人員才應該對這個包感興趣。
JNDI 上下文
承前所述,命名服務是將名稱與對象相關聯。這種關聯被稱爲綁定。一組這樣的綁定被稱爲上下文,它提供返回對象的分解或查找操作。其他操作還可能包括綁定與解除綁定名稱,以及列出被綁定的名稱。注意,可以將一個上下文對象中的名稱綁定到具有同樣命名慣例的另一個上下文對象上。這被稱爲子上下文。例如,如果UNIX目錄/home 是一個上下文,那麼名稱與其相關的目錄便是子上下文。例如,/home/guests.,這裏的guests 便是 home的一個子上下文。
在JNDI中,上下文是使用javax.naming.Context 接口來表示的,而這個接口也正是與命名服務進行交互的主要接口。Context (或稍後將要討論的DirContext)接口中的每個命名方法都有兩種重載的形式:
- lookup(String name): 接受一個字符串名稱。
- lookup(javax.naming.Name): 接受一個結構化的名稱,比如CompositeName (一個跨越多個命名系統的名稱)或 CompondName (一個位於單個命名系統中的名稱);二者均實現了Name 接口。下面是一個複合名稱的例子: cn=mydir,cn=Q Mahmoud,ou=People,還有一個組合名稱的例子: cn=mydir,cn=Q Mahmoud,ou=People/myfiles/max.txt (這裏的myfiles/max.txt 是代表第二部分的一個文件名)。
javax.naming.InitialContext 是一個實現了 Context接口的類。使用這個類作爲您到命名服務的入口點。要創建一個InitialContext 對象,構造器需要採用一組屬性,形式爲java.util.Hashtable 或其子類之一,比如Properties.。下面是一個例子:
Hashtable env = new Hashtable(); // select a service provider factory env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContext"); // create the initial context Context contxt = new InitialContext(env); |
INITIAL_CONTEXT_FACTORY 指定JNDI服務提供程序中工廠類的名稱。該工廠負責爲其服務創建一個合適的InitialContext 對象。在上面的代碼片斷中,指定了用於文件系統服務提供程序的一個工廠類。表1列出了用於所支持的服務提供程序的工廠類。注意,用於文件系統服務提供程序的工廠類需要從Sun Microsystems單獨下載,它並沒有與J2SE 1.4.x一起發行。
表 1: Context.INITIAL_CONTEXT_FACTORY的值 |
|
名稱 |
服務提供程序工廠 |
文件系統 |
com.sun.jndi.fscontext.RefFSContextFactory |
LDAP |
com.sun.jndi.ldap.LdapCtxFactory |
RMI |
com.sun.jndi.rmi.registry.RegistryContextFactory |
CORBA |
com.sun.jndi.cosnaming.CNCtxFactory |
DNS |
com.sun.jndi.dns.DnsContextFactory |
要通過來自命名或目錄服務的名稱檢索或解析(查找)一個對象,使用Context: Object obj = contxt.lookup(name)的lookup方法。lookup 方法返回一個對象,該對象代表您想要查找的上下文的子上下文。
一個命名的例子
現在,讓我們看一看一個使用命名服務的例子。在這個例子中,我們編寫了一個簡單的程序,用於查找一個其名稱被當作命令行參數傳入的對象。在這裏,我們將使用一個用於文件系統的服務提供程序,而且因此,我們提供作爲參數的名稱必須是一個文件名。示例代碼1中給出了相應代碼。
示例代碼 1: Resolve.java
import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import java.util.Hashtable; public class Resolve { public static void main(String argv[]) { // The user should provide a file to lookup if (argv.length != 1) { System.err.println("Usage: java Resolve "); System.exit(-1); } String name = argv[0]; // Here we use the file system service provider Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory"); try { // Create the initial context Context ctx = new InitialContext(env); // Look up an object Object obj = ctx.lookup(name); // Print it out System.out.println(name + " is bound to: " + obj); // Close the context ctx.close(); } catch (NamingException e) { System.err.println("Problem looking up " + name + ": " + e); } } } |
在這裏,我假定您使用的是Java 2SDK 1.4.x,它附帶有幾個服務提供程序(上面已經列出)。這個應用程序要使用文件系統服務提供程序 ,而在默認情況下,文件系統服務提供程序並未安裝。因此,您需要下載並安裝它。另一方面,如果您運行這個程序,而服務提供程序卻還沒有被安裝,您將得到一個NoInitialContextException,意指無法找到服務提供程序工廠類,因此不能初始化這個類。接着,您需要在您的classpath中包括fscontext.jar 和providerutil.jar——或者像我一樣,您可以簡單地將這兩個文件拷貝至JAVA_HOME/jre/lib/ext,這裏的 JAVA_HOME 是指您的Java 2SDK安裝的根目錄。
要測試這個應用程序:
1. 確保您已經下載並安裝了文件系統服務提供程序(正如上一段所講的那樣),因爲這個服務提供程序並沒有與J2SE 1.4.x一起提供。
2. 拷貝代碼並將其粘貼到文件中,並將文件命名爲Resolve.java。
3. 使用javac 編譯 Resolve.java 。
4. 使用java 解釋器運行應用程序。
下面是一次示範運行:
prompt> java Resolve /classes /classes is bound to: com.sun.jndi.fscontext.FSContext@f62373 |
如果您提供的名稱是一個文件名,您將看到如下結果:
prompt> java Resolve /classes/Resolve.java
/classes/Resolve.java is bound to: C:/classes/Resolve.java |
列出文件目錄的內容
現在,讓我們看一看如何使用其他JNDI API列出一個文件目錄的內容。我們假定,您想讓用戶能夠使用file:///這樣的URL 來指定命令行參數。在這種情況下,您要設置一個新的屬性PROVIDER_URL,如示例代碼2所示。Context 的listBindings 方法返回一個 NamingEnumeration對象,可以通過使用一個while 循環來迭代這個對象,如示例代碼 2所示。
示例代碼 2 Resolve2.java
import javax.naming.Binding; import javax.naming.NamingEnumeration; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import java.util.Hashtable; public class Resolve2 { public static void main(String argv[]) { // The user should provide a file to lookup if (argv.length != 1) { System.err.println("Usage: java Resolve2 "); System.exit(-1); } // Here we use the file system service provider Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.FSContextFactory"); env.put(Context.PROVIDER_URL, argv[0]); try { // Create the initial context Context ctx = new InitialContext(env); NamingEnumeration ne = ctx.listBindings(""); while(ne.hasMore()) { Binding b = (Binding) ne.next(); System.out.println(b.getName() + " " + b.getObject()); } // close the context ctx.close(); } catch (NamingException e) { System.err.println("Problem looking up " + argv[0] + ": " + e); } } } |
要測試這個應用程序,遵照與上一個例子同樣的編輯和運行步驟即可。下面是一次示範運行:
prompt>: java Resolve2 file:///uddi fig1.gif C:/uddi/fig1.gif fig2.gif C:/uddi/fig2.gif fig3.gif C:/uddi/fig3.gif fig4.gif C:/uddi/fig4.gif fig5.gif C:/uddi/fig5.gif impl.txt C:/uddi/impl.txt |
目錄服務
承前所述,目錄服務便是其對象具有屬性及名稱的命名服務。具有屬性和名稱的對象被稱爲目錄入口。應用程序可以使用目錄服務存儲和檢索目錄對象的屬性。它甚至可以被用於對象存儲。
LDAP
輕量級目錄訪問協議(LDAP)來源於X.500 協議(由位於Ann Arbor的密歇根大學開發),是一個用於訪問和管理目錄服務的協議;它定義了客戶端應該如何訪問存儲在服務器上的數據,但沒有定義應該如何存儲數據。LDAP目錄由帶有描述性信息的入口組成,這些描述性信息描述了人(例如,姓名、電話號碼、電子郵件地址,等等)或網絡資源(比如打印機、傳真機之類的)。這類描述性信息被存儲在一個入口的屬性中,入口的每個屬性均描述了一種特定類型的信息。下面給出一個例子,內容是用於描述一個人的屬性:
cn: Qusay H. Mahmoud mail: [email protected] telephoneNumber: 123-4567 |
LDAP 目錄服務可以用於基於屬性查找某個人的電話號碼或電子郵件地址。表2列出了一些常見的LDAP 屬性:
表 2: 一些常見的 LDAP 屬性 |
|
屬性 |
意義 |
o |
組織 |
cn |
常用名 |
sn |
姓 |
uid |
用戶id |
|
電子郵件地址 |
c |
國家 |
LDAP名稱是一個 (名稱,值) 對的序列,比如姓名、組織、國家。
cn=Qusay Mahmoud, o=javacourses.com, c= |
javax.naming.directory.DirContext是一個JNDI的目錄服務接口,它擴展了javax.naming.Context。它提供的方法有:
- search: 搜索匹配目錄入口的目錄,並比較一個目錄入口和一組屬性。
- bind 和createSubcontext: 添加一個新的目錄入口。
- modifyAttributes: 修改一個目錄入口的特定屬性。rename 方法可以用於修改入口名稱本身。
- unbind 和destroySubcontext: 刪除一個特定的目錄入口。
- close: 結束與一臺LDAP服務器的會話。
使用JNDI 進行LDAP編程
要操作一臺LDAP 服務器(比如Sun ONE Directory Server)中的對象,您必須首先連接到該服務器;您可能還需要使您自己通過服務器的身份驗證。要連接到服務器,您可以從DirContext 接口獲得對一個對象的引用。使用InitialDirContext 類可以做到這一點,而該類需要一個 Hashtable。
下面的代碼片斷可以使用戶通過一臺LDAP服務器的身份驗證,並連接到該服務器上。注意,這裏使用的是簡單的身份驗證。簡單身份驗證包括把用戶的完全限定的DN和用戶的明文口令發送給LDAP 服務器。要避免暴露明文口令,使用帶有加密通道的SSL機制,如果您的LDAP服務器支持這種機制的話。想要了解關於身份驗證模式的更多信息,請參見 JNDI Tutorial。
Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); // specify where the ldap server is running env.put(Context.PROVIDER_URL, "ldap://GH308C-N-MAHMOUD.humber.org:61596"); env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager"); env.put(Context.SECURITY_CREDENTIALS, "password"); // Create the initial directory context DirContext ctx = new InitialDirContext(env); |
連接到LDAP 服務器上之後,您可以在LDAP服務器上添加新的入口、或者修改、刪除、搜索一個入口。下面的代碼片斷說明了如何添加或存儲一個新的入口。注意:要存儲一個對象,您需要使用Java Schema裝載它,而 Java Schema並沒有在目錄服務器上被預配置。想要了解關於此點的更多信息,請參見JNDI指南中的Java Objects and the Directory 部分。
SomeObject Obj = new SomeObjct("param1", "param2", "param3"); ctx.bind("cn=myobject", obj); |
您可以使用lookup 方法查找一個對象,如下:
SomeObject obj = (SomeObject) ctx.lookup("cn=myobject"); |
示例代碼3 給出了一個如何檢索命名對象的屬性的例子。正如您所看到的那樣,用於選擇工廠類的代碼與前面相同。我們使用InitialDirContext 類創建了一個目錄上下文DirContext,getAttributes 方法用於返回對象的屬性,而最後,get方法找到了姓並打印之。相當直觀,是不是?
示例代碼 3: GetAttrib.java
import javax.naming.Context; import javax.naming.directory.InitialDirContext; import javax.naming.directory.DirContext; import javax.naming.directory.Attributes; import javax.naming.NamingException; import java.util.Hashtable; class GetAttrib { public static void main(String[] argv) { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); // specify where the ldap server is running env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=javacourses.com,c= // use simple authenticate to authenticate the user env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager"); env.put(Context.SECURITY_CREDENTIALS, "password"); try { // Create the initial directory context DirContext ctx = new InitialDirContext(env); // Ask for all attributes of the object Attributes attrs = ctx.getAttributes("cn=Qusay Mahmoud"); // Find the surname ("sn") attribute of this object and print it System.out.println("Last Name: " + attrs.get("sn").get()); // Close the context ctx.close(); } catch (NamingException e) { System.err.println("Problem getting attribute: " + e); } } } |
JNDI提供用於進行基本和高級(使用過濾器)搜索的 API 。例如,使用一組入口必須具有的屬性,以及要在其中執行搜索的目標上下文,便可以執行一次簡單的搜索。下面的代碼片斷說明了如何在一棵子樹中搜索一個具有uid=qmahmoud 屬性的入口。使用過濾器的高級搜索不在本文的討論範圍之內。
// ignore attribute name case Attributes matchattribs = new BasicAttributes(true); matchattribs.put(new BasicAttribute("uid", "qmahmoud")); // search for objects with those matching attributes NamingEnumeration answer = ctx.search("ou=People,o=javacourses.com", matchattribs); while (answer.hasMore()) { SearchResult sr = (SearchResult)answer.next(); // print the results you need } |
想要了解使用JNDI編寫LDAP 客戶端方面的更多信息,請參見Tips for LDAP Users。
JNDI 的CORBA COS命名服務提供程序
CORBA 公共對象服務 (COS) 名稱服務器用於存儲CORBA對象引用。您可以使用COS命名包(org.omg.CORBA.CosNaming)在CORBA 應用程序中訪問它。
JNDI COS命名服務提供程序基於COS命名包實現了javax.naming.Context 接口,這樣CORBA 應用程序就能夠使用JNDI訪問 COS 名稱服務器。因此,使用 JNDI 的CORBA 應用程序具有一個用於訪問所有命名和目錄服務的接口。這使得CORBA應用程序能夠使用像LDAP這樣的分佈式企業級服務來存儲對象引用。
要選擇COS 命名服務提供程序,使用:
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.cosnaming.CNCtxFactory"); |
要轉換您的CORBA 應用程序以使用JNDI,考慮AddServer.java 和AddClient.java,它們在另一篇文章中有更加詳細的描述。
1. 在客戶端和服務器中均使用javax.naming,將:
import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; |
替換爲:
import javax.naming.*; |
2. 在客戶端和服務器中使用InitialContext 代替 NameService :
將:
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef); |
替換爲:
Hashtable env = new Hashtable(); env.put("java.naming.corba.orb", orb); Context ctx = new InitialContext(env); |
3. 使用lookup 代替resolve:
將:
String name = "Add"; Add href = AddHelper.narrow(ncRef.resolve_str(name)); |
替換爲:
Add href = AddHelper.narrow((org.omg.CORBA.Object)ctx.lookup("Add")); |
JNDI 的RMI 註冊表服務提供程序
RMI 註冊表服務提供程序允許JNDI 應用程序訪問使用RMI註冊表註冊的遠程對象。已知註冊表所在的位置之後,提供程序使用綁定爲註冊在註冊表中的對象創建一個命名上下文。接下來,這個上下文可以被綁定到另一個JNDI可訪問的命名空間中,比如LDAP。這項新功能包含了java.rmi.Naming 類提供的功能。
這樣使用RMI的主要優點是,客戶端不再需要知道RMI註冊表運行之處的主機名和端口號;它與位置無關。
下面的代碼片斷說明了如何將JNDI 與 RMI一起使用:
// select the registry service provider as the initial context env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); // specify where the registry is running env.put(Context.PROVIDER_URL, "rmi://server:1099"); // create an initial context that accesses the registry Context ctx = new InitialContext(env); // now, the names stored in registry can be listed NamingEnumeration enum = ctx.list(""); // bind the registry context into LDAP directory Context ldapctx = (Context)ctx.lookup("ldap://server:port/o=comp,c=ca"); ldapctx.bind("cn=rmi", ctx); |
JNDI 的DNS 服務提供程序
DNS服務提供程序使得基於JNDI的應用程序能夠訪問存儲在DNS中的信息。DNS服務提供程序將DNS命名空間呈現爲JNDI 目錄上下文的一棵樹,而將DNS 資源記錄呈現爲JNDI 屬性。
示例代碼 4 演示瞭如何使用DNS 服務提供程序檢索環境和IP地址(A記錄)的信息。
示例代碼 4: TestDNS.java
import javax.naming.*; import com.sun.jndi.dns.*; import java.util.Hashtable; public class TestDNS { public static void main(String[] argv) { Name cn = null; String name = argv[0]; Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory"); env.put(Context.PROVIDER_URL, "dns://IP for DNS Server/"); try { // Create the initial context Context ctx = new InitialContext(env); // print the fully qualified name (.) System.out.println("Name in namespace: "+ctx.getNameInNamespace()); // retrieve the parser associated with the named context NameParser np = ctx.getNameParser(ctx.getNameInNamespace()); if (argv.length != 1) { System.out.println("Usage: java TestDNS "); System.exit(-1); } // parse the name into its components and print them cn = np.parse(name); System.out.println("Name is: "+cn.toString()); System.out.println("The parsed name has "+cn.size()+" components:"); for (int i=0; i<cn.size(); i++){ System.out.println(cn.get(i)); } System.out.print("Trying to lookup "); // get the prefix (domain) and suffix (hostname) Name domain = cn.getPrefix(cn.size()-1); Name host = cn.getSuffix(cn.size()-1); System.out.println("DNS Host: "+host+" Domain: "+domain); // retrieve the named object Object obj = ctx.lookup(domain); System.out.println(domain.toString()+" is bound to: "+obj); // retrieve and print the environment in effect System.out.println("Domain properties: "+ ((Context)obj).getEnvironment()); // retrieve and print the IP address (the DNS A records) System.out.println("IP for: "+cn+ " is: "+ ((DnsContext)obj).getAttributes(host, new String[]{"A"})); // we're done so close the context ctx.close(); } catch (NamingException e) { System.err.println("Problem looking up " + cn + ": " + e); } } } |
在您運行這個應用程序之前,確保您指定了DNS服務器的IP地址。
下面是一次示範運行:
prompt> java TestDNS prep.ai.mit.edu Name in namespace: . Name is: prep.ai.mit.edu The parsed name has 4 components: edu mit ai prep Trying to lookup DNS Host: prep Domain: ai.mit.edu ai.mit.edu is bound to: com.sun.jndi.dns.DnsContext@b89838 Domain properties: {java.naming.provider.url=dns://IP for DNS Server/, java.namin g.factory.initial=com.sun.jndi.dns.DnsContextFactory} IP for: prep.ai.mit.edu is: {a=A: 199.232.41.9} |
JNDI 和J2EE
JNDI 是J2EE平臺的標準服務API之一。包含它的目的是爲應用程序組件提供一個標準的API,用於引用資源和其他應用程序組件。J2EE還定義了一種標準的命名策略(邏輯和真實的名稱),以和 JNDI一起使用,這樣就可以採用一種與部署環境無關的方式編寫應用程序。您可以引用 J2EE服務,具體方法是根據其邏輯名稱在目錄中查找它們。爲了實現這一點,每個符合 J2EE規範的系統均提供了一個稱爲環境的JNDI 服務,該環境包括:
- 環境變量
- EJB 引用
- 資源工廠引用
注意:在這裏,我只討論環境變量。想要了解EJB引用和資源工廠引用方面的更多信息,請參見這篇文章 。
環境變量
應用程序組件的命名環境允許您定製應用程序組件,而不需要訪問或修改組件的源代碼。每個應用程序組件定義它自己的環境入口的集合。同一個容器中,一個應用程序組件的所有實例共享同一個入口。注意,不允許應用程序組件實例在運行時修改環境。
聲明環境變量
應用程序組件提供程序必須聲明從應用程序的組件代碼訪問的所有環境入口。它們是在部署描述器(例如Tomcat中的web.xml)中通過使用<env-entry> 標籤來聲明的. <env-entry> 標籤的元素有:
- <description>:環境入口的可選描述。
- <env-entry-name>: 環境入口名。
- <env-entry-type>:期望的環境變量類型。它可以是以下 Java 類型之一: Boolean、Byte、 Double、Character、 Float、 Integer、 Long、Short、String 。
- <env-entry-value>: 必須匹配所提供類型的環境入口值。
- <env-entry-type>. 這個值可以稍後改變,但是如果它沒有被設定,那麼在部署期間必須爲它指定一個值。
示例代碼 5中的例子給出了兩個環境入口的聲明。要爲一個新環境指定一個聲明,您只要把它添加給您的web應用程序描述器 (web.xml) 即可。
示例代碼 5: 聲明環境變量
<env-entry> <description>welcome message</description> <env-entry-name>greetings</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>Welcome to the Inventory Control System</env-entry-value> </env-entry> <env-entry> <description>maximum number of products</descriptor> <env-entry-name>inventory/max</env-entry-name> <env-entry-type>java.lang.Integer</env-entry-type> <env-entry-value>27</env-entry-value> </env-entry> |
每個<env-entry>標籤描述一個環境入口。所以在這個例子中,定義了兩個環境入口。第一個叫做greetings, 是 String 類型,初始的默認值爲: Welcome to the Inventory Control System。第二個入口叫做 inventory/max,是 Integer類型,初始的默認值爲 27。
現在,應用程序組件實例可以使用JNDI定位環境入口. 它使用不帶參數的構造器創建了一個javax.naming.InitialContext 對象。接着,它通過InitialContext查找命名環境,所使用的是以java:comp/env打頭的JNDI URL。示例代碼 6 說明了一個應用程序組件如何訪問它的環境入口。
示例代碼 6: 訪問環境入口
// obtain the application component's environment // naming context javax.naming.Context ctx = new javax.naming.InitialContext(); javax.naming.Context env = ctx.lookup("java:comp/env"); // obtain the greetings message //configured by the deployer String str = (String) env.lookup("greetings"); // use the greetings message System.out.println(greetings); // obtain the maximum number of products //configured by the deployer Integer maximum = (Integer) env.lookup( "inventory/max"); //use the entry to customize business logic |
注意,應用程序組件還可以使用如下的完整路徑名查找環境入口:
javax.naming.Context ctx = new javax.naming.InitialContext(); String str = (String) ctx.lookup( "java:comp/env/greetings"); |
這段代碼片斷可以用在一個 JSP 頁面中,如 示例代碼 7所示:
示例代碼 7: 從一個 JSP頁面訪問環境入口
<HTML> <HEAD> <TITLE>JSP Example</TITLE> </HEAD> <BODY BGCOLOR="#ffffcc"> <CENTER> <H2>Inventory System</H2> <% javax.naming.Context ctx = new javax.naming.InitialContext(); javax.naming.Context myenv = (javax.naming.Context) t.lookup("java:comp/env"); java.lang.String s = (java.lang.String) myenv.lookup("greetings"); out.println("The value is: "+greetings); %> </CENTER> </BODY> </HTML> |
結束語
JNDI是使用命名/目錄服務增強您的網絡應用程序的一組 API。本文通篇給出的例子演示了,開始使用JNDI開發基於目錄的應用程序是一件多麼簡單的事情。這些例子還演示了 如何使用相同的API訪問不同的命名/目錄服務。開發人員不必學習不同的 API。在某些情況下,比如在 RMI 和 CORBA應用程序中, JNDI 允許您將命名服務的選擇延至部署階段。
對JNDI 未來的期望有: 與標準的Java SASL API (JSR-28)結合,支持國際化的域名,而且支持安全的 DNS 。
要開始學習和使用JNDI 和 LDAP,下載Sun ONE Directory Server的試用版,它可以用於各種平臺和各種語言。
更多信息
LDAP v3
JNDI
The JNDI Tutorial
JNDI Service Providers
Sun ONE Directory Server
JNDI-INTEREST Mailing List (for discussing JNDI)
致謝
特別感謝Sun Microsystems的 Jaya Hangal 和 Rosanna Lee,他們的反饋幫助我完善了本文。