Java命名和目錄接口(Java Naming and Directory Interface ,JNDI)是用于從Java應用程序中訪(fǎng)問(wèn)名稱(chēng)和目錄服務(wù)的一組API。命名服務(wù)即將名稱(chēng)與對象相關(guān)聯(lián),以便能通過(guò)相應名稱(chēng)訪(fǎng)問(wèn)這些對象。而目錄服務(wù)即其對象具有屬性及名稱(chēng)的命名服務(wù)。
命名或目錄服務(wù)允許您集中管理共享信息的存儲,這在網(wǎng)絡(luò )應用程序中很重要,因為它可以使這類(lèi)應用程序更加一致和易于管理。例如,可以將打印機配置存儲在目錄服務(wù)中,這樣所有與打印機相關(guān)的應用程序都能夠使用它。
本文是一份代碼密集型的快速入門(mén)指南,讓您開(kāi)始了解和使用JNDI。它:
JNDI綜述
我們所有人每天都在不自知的情況下使用命名服務(wù)。例如,當您在瀏覽器中輸入URL http://java.sun.com 時(shí),域名系統(Domain Name System ,DNS)將這個(gè)以符號表示的URL轉換為一個(gè)通信標識符(IP地址)。在命名系統中,對象的范圍可以從位于DNS記錄中的名稱(chēng)變動(dòng)到應用程序服務(wù)器中的企業(yè)JavaBeans組件(Enterprise JavaBeans Components ,EJBs),還可以到輕量級目錄訪(fǎng)問(wèn)協(xié)議(Lightweight Directory Access Protocol ,LDAP)中的用戶(hù)配置文件。
目錄服務(wù)是命名服務(wù)的自然擴展。二者的關(guān)鍵區別在于,目錄服務(wù)允許屬性(比如用戶(hù)的電子郵件地址)與對象相關(guān)聯(lián),而命名服務(wù)則不然。這樣,使用目錄服務(wù)時(shí),您可以基于對象的屬性來(lái)搜索它們。JNDI允許您訪(fǎng)問(wèn)文件系統中的文件,定位遠程RMI注冊表中的對象,訪(fǎng)問(wèn)諸如LDAP這樣的目錄服務(wù),并定位網(wǎng)絡(luò )上的EJB。
很多應用程序選擇使用JNDI都可以收到良好的效果,比如LDAP客戶(hù)端、應用程序啟動(dòng)器、類(lèi)瀏覽器、網(wǎng)絡(luò )管理實(shí)用工具,或者甚至是地址簿。
JNDI架構
JNDI架構提供了一個(gè)標準的、與命名系統無(wú)關(guān)的API,這個(gè)API構建在特定于命名系統的驅動(dòng)程序之上。這一層幫助把應用程序和實(shí)際的數據源隔離開(kāi)來(lái),因此無(wú)論應用程序是訪(fǎng)問(wèn)LDAP、RMI、DNS還是其他的目錄服務(wù),這都沒(méi)有關(guān)系。換句話(huà)說(shuō),JNDI與任何特定的目錄服務(wù)實(shí)現無(wú)關(guān),您可以使用任何目錄,只要您擁有相應的服務(wù)提供程序接口(或驅動(dòng)程序)即可,如圖1所示。

圖1: JNDI架構
注意,關(guān)于JNDI有一點(diǎn)很重要,即它同時(shí)提供應用程序編程接口(Application Programming Interface ,API)和服務(wù)提供程序接口(Service Provider Interface ,SPI)。這樣做的實(shí)際意義在于,對于您的與命名或目錄服務(wù)交互的應用程序來(lái)說(shuō),必須存在用于該服務(wù)的一個(gè)JNDI服務(wù)提供程序,這便是JNDI SPI發(fā)揮作用的舞臺。一個(gè)服務(wù)提供程序基本上就是一組類(lèi),這些類(lèi)針對特定的命名和目錄服務(wù)實(shí)現了各種JNDI接口——這與JDBC驅動(dòng)程序針對特定的數據系統實(shí)現各種JDBC接口極為相似。作為一名應用程序開(kāi)發(fā)人員,您不需要擔心JNDI SPI.。您只需確保,您為每個(gè)想使用的命名或目錄服務(wù)提供了一個(gè)服務(wù)提供程序。
J2SE和JNDI
JNDI被包含在Java 2 SDK 1.3 及其更新版本中。它還可以用作JDK 1.1和1.2的一個(gè)標準擴展。 Java 2 SDK 1.4.x的最新版本進(jìn)行了改進(jìn),將以下命名/目錄服務(wù)提供程序包括進(jìn)來(lái):
有關(guān)服務(wù)提供程序的更多內容
在這里可以下載一系列服務(wù)提供程序。Windows注冊表JNDI 提供程序(來(lái)自cogentlogic.com)可能會(huì )引起您特別的興趣,因為它允許您訪(fǎng)問(wèn)Windows XP/2000/NT/Me/9x上的注冊表。
此外,還可以下載JNDI/LDAP Bootster Pack。這個(gè)增強補丁包含對流行的LDAP控件和擴展的支持。它代替了與LDAP 1.2.1服務(wù)提供程序捆綁在一起的增強補丁。參見(jiàn) Controls and Extensions 以獲得更多信息。
另一個(gè)要考察的有趣的服務(wù)提供程序是Sun的Directory Services Markup Language (DSML) v2.0提供程序。 DSML的目標是將目錄服務(wù)與XML連接起來(lái)
JNDI API
JNDI API 包括5個(gè)包:
JNDI 上下文
承前所述,命名服務(wù)是將名稱(chēng)與對象相關(guān)聯(lián)。這種關(guān)聯(lián)被稱(chēng)為綁定。一組這樣的綁定被稱(chēng)為上下文,它提供返回對象的分解或查找操作。其他操作還可能包括綁定與解除綁定名稱(chēng),以及列出被綁定的名稱(chēng)。注意,可以將一個(gè)上下文對象中的名稱(chēng)綁定到具有同樣命名慣例的另一個(gè)上下文對象上。這被稱(chēng)為子上下文。例如,如果UNIX目錄/home 是一個(gè)上下文,那么名稱(chēng)與其相關(guān)的目錄便是子上下文。例如,/home/guests.,這里的guests 便是 home的一個(gè)子上下文。
在JNDI中,上下文是使用javax.naming.Context 接口來(lái)表示的,而這個(gè)接口也正是與命名服務(wù)進(jìn)行交互的主要接口。Context (或稍后將要討論的DirContext)接口中的每個(gè)命名方法都有兩種重載的形式:
javax.naming.InitialContext 是一個(gè)實(shí)現了 Context接口的類(lèi)。使用這個(gè)類(lèi)作為您到命名服務(wù)的入口點(diǎn)。要創(chuàng )建一個(gè)InitialContext 對象,構造器需要采用一組屬性,形式為java.util.Hashtable 或其子類(lèi)之一,比如Properties.。下面是一個(gè)例子:
| 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服務(wù)提供程序中工廠(chǎng)類(lèi)的名稱(chēng)。該工廠(chǎng)負責為其服務(wù)創(chuàng )建一個(gè)合適的InitialContext 對象。在上面的代碼片斷中,指定了用于文件系統服務(wù)提供程序的一個(gè)工廠(chǎng)類(lèi)。表1列出了用于所支持的服務(wù)提供程序的工廠(chǎng)類(lèi)。注意,用于文件系統服務(wù)提供程序的工廠(chǎng)類(lèi)需要從Sun Microsystems單獨下載,它并沒(méi)有與J2SE 1.4.x一起發(fā)行。
| 表 1: Context.INITIAL_CONTEXT_FACTORY的值 | |
| 名稱(chēng) | 服務(wù)提供程序工廠(chǎng) |
| 文件系統 | 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 |
要通過(guò)來(lái)自命名或目錄服務(wù)的名稱(chēng)檢索或解析(查找)一個(gè)對象,使用Context: Object obj = contxt.lookup(name)的lookup方法。lookup 方法返回一個(gè)對象,該對象代表您想要查找的上下文的子上下文。
一個(gè)命名的例子
現在,讓我們看一看一個(gè)使用命名服務(wù)的例子。在這個(gè)例子中,我們編寫(xiě)了一個(gè)簡(jiǎn)單的程序,用于查找一個(gè)其名稱(chēng)被當作命令行參數傳入的對象。在這里,我們將使用一個(gè)用于文件系統的服務(wù)提供程序,而且因此,我們提供作為參數的名稱(chēng)必須是一個(gè)文件名。示例代碼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,它附帶有幾個(gè)服務(wù)提供程序(上面已經(jīng)列出)。這個(gè)應用程序要使用文件系統服務(wù)提供程序 ,而在默認情況下,文件系統服務(wù)提供程序并未安裝。因此,您需要下載并安裝它。另一方面,如果您運行這個(gè)程序,而服務(wù)提供程序卻還沒(méi)有被安裝,您將得到一個(gè)NoInitialContextException,意指無(wú)法找到服務(wù)提供程序工廠(chǎng)類(lèi),因此不能初始化這個(gè)類(lèi)。接著(zhù),您需要在您的classpath中包括fscontext.jar 和providerutil.jar——或者像我一樣,您可以簡(jiǎn)單地將這兩個(gè)文件拷貝至JAVA_HOME\jre\lib\ext,這里的 JAVA_HOME 是指您的Java 2SDK安裝的根目錄。
要測試這個(gè)應用程序:
1. 確保您已經(jīng)下載并安裝了文件系統服務(wù)提供程序(正如上一段所講的那樣),因為這個(gè)服務(wù)提供程序并沒(méi)有與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 |
如果您提供的名稱(chēng)是一個(gè)文件名,您將看到如下結果:
prompt> java Resolve \classes\Resolve.java
| \classes\Resolve.java is bound to: C:\classes\Resolve.java |
列出文件目錄的內容
現在,讓我們看一看如何使用其他JNDI API列出一個(gè)文件目錄的內容。我們假定,您想讓用戶(hù)能夠使用file:///這樣的URL 來(lái)指定命令行參數。在這種情況下,您要設置一個(gè)新的屬性PROVIDER_URL,如示例代碼2所示。Context 的listBindings 方法返回一個(gè) NamingEnumeration對象,可以通過(guò)使用一個(gè)while 循環(huán)來(lái)迭代這個(gè)對象,如示例代碼 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); } } } |
要測試這個(gè)應用程序,遵照與上一個(gè)例子同樣的編輯和運行步驟即可。下面是一次示范運行:
| 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 |
目錄服務(wù)
承前所述,目錄服務(wù)便是其對象具有屬性及名稱(chēng)的命名服務(wù)。具有屬性和名稱(chēng)的對象被稱(chēng)為目錄入口。應用程序可以使用目錄服務(wù)存儲和檢索目錄對象的屬性。它甚至可以被用于對象存儲。
LDAP
輕量級目錄訪(fǎng)問(wèn)協(xié)議(LDAP)來(lái)源于X.500 協(xié)議(由位于Ann Arbor的密歇根大學(xué)開(kāi)發(fā)),是一個(gè)用于訪(fǎng)問(wèn)和管理目錄服務(wù)的協(xié)議;它定義了客戶(hù)端應該如何訪(fǎng)問(wèn)存儲在服務(wù)器上的數據,但沒(méi)有定義應該如何存儲數據。LDAP目錄由帶有描述性信息的入口組成,這些描述性信息描述了人(例如,姓名、電話(huà)號碼、電子郵件地址,等等)或網(wǎng)絡(luò )資源(比如打印機、傳真機之類(lèi)的)。這類(lèi)描述性信息被存儲在一個(gè)入口的屬性中,入口的每個(gè)屬性均描述了一種特定類(lèi)型的信息。下面給出一個(gè)例子,內容是用于描述一個(gè)人的屬性:
| cn: Qusay H. Mahmoud mail: qmahmoud@javacourses.com telephoneNumber: 123-4567 |
LDAP 目錄服務(wù)可以用于基于屬性查找某個(gè)人的電話(huà)號碼或電子郵件地址。表2列出了一些常見(jiàn)的LDAP 屬性:
| 表 2: 一些常見(jiàn)的 LDAP 屬性 | |
| 屬性 | 意義 |
| o | 組織 |
| cn | 常用名 |
| sn | 姓 |
| uid | 用戶(hù)id |
| | 電子郵件地址 |
| c | 國家 |
LDAP名稱(chēng)是一個(gè) (名稱(chēng),值) 對的序列,比如姓名、組織、國家。
| cn=Qusay Mahmoud, o=javacourses.com, c= |
javax.naming.directory.DirContext是一個(gè)JNDI的目錄服務(wù)接口,它擴展了javax.naming.Context。它提供的方法有:
使用JNDI 進(jìn)行LDAP編程
要操作一臺LDAP 服務(wù)器(比如Sun ONE Directory Server)中的對象,您必須首先連接到該服務(wù)器;您可能還需要使您自己通過(guò)服務(wù)器的身份驗證。要連接到服務(wù)器,您可以從DirContext 接口獲得對一個(gè)對象的引用。使用InitialDirContext 類(lèi)可以做到這一點(diǎn),而該類(lèi)需要一個(gè) Hashtable。
下面的代碼片斷可以使用戶(hù)通過(guò)一臺LDAP服務(wù)器的身份驗證,并連接到該服務(wù)器上。注意,這里使用的是簡(jiǎn)單的身份驗證。簡(jiǎn)單身份驗證包括把用戶(hù)的完全限定的DN和用戶(hù)的明文口令發(fā)送給LDAP 服務(wù)器。要避免暴露明文口令,使用帶有加密通道的SSL機制,如果您的LDAP服務(wù)器支持這種機制的話(huà)。想要了解關(guān)于身份驗證模式的更多信息,請參見(jiàn) 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 服務(wù)器上之后,您可以在LDAP服務(wù)器上添加新的入口、或者修改、刪除、搜索一個(gè)入口。下面的代碼片斷說(shuō)明了如何添加或存儲一個(gè)新的入口。注意:要存儲一個(gè)對象,您需要使用Java Schema裝載它,而 Java Schema并沒(méi)有在目錄服務(wù)器上被預配置。想要了解關(guān)于此點(diǎn)的更多信息,請參見(jiàn)JNDI指南中的Java Objects and the Directory 部分。
| SomeObject Obj = new SomeObjct("param1", "param2", "param3"); ctx.bind("cn=myobject", obj); |
您可以使用lookup 方法查找一個(gè)對象,如下:
| SomeObject obj = (SomeObject) ctx.lookup("cn=myobject"); |
示例代碼3 給出了一個(gè)如何檢索命名對象的屬性的例子。正如您所看到的那樣,用于選擇工廠(chǎng)類(lèi)的代碼與前面相同。我們使用InitialDirContext 類(lèi)創(chuàng )建了一個(gè)目錄上下文DirContext,getAttributes 方法用于返回對象的屬性,而最后,get方法找到了姓并打印之。相當直觀(guān),是不是?
示例代碼 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提供用于進(jìn)行基本和高級(使用過(guò)濾器)搜索的 API 。例如,使用一組入口必須具有的屬性,以及要在其中執行搜索的目標上下文,便可以執行一次簡(jiǎn)單的搜索。下面的代碼片斷說(shuō)明了如何在一棵子樹(shù)中搜索一個(gè)具有uid=qmahmoud 屬性的入口。使用過(guò)濾器的高級搜索不在本文的討論范圍之內。
| // 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編寫(xiě)LDAP 客戶(hù)端方面的更多信息,請參見(jiàn)Tips for LDAP Users。
JNDI 的CORBA COS命名服務(wù)提供程序
CORBA 公共對象服務(wù) (COS) 名稱(chēng)服務(wù)器用于存儲CORBA對象引用。您可以使用COS命名包(org.omg.CORBA.CosNaming)在CORBA 應用程序中訪(fǎng)問(wèn)它。
JNDI COS命名服務(wù)提供程序基于COS命名包實(shí)現了javax.naming.Context 接口,這樣CORBA 應用程序就能夠使用JNDI訪(fǎng)問(wèn) COS 名稱(chēng)服務(wù)器。因此,使用 JNDI 的CORBA 應用程序具有一個(gè)用于訪(fǎng)問(wèn)所有命名和目錄服務(wù)的接口。這使得CORBA應用程序能夠使用像LDAP這樣的分布式企業(yè)級服務(wù)來(lái)存儲對象引用。
要選擇COS 命名服務(wù)提供程序,使用:
| env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.cosnaming.CNCtxFactory"); |
要轉換您的CORBA 應用程序以使用JNDI,考慮AddServer.java 和AddClient.java,它們在另一篇文章中有更加詳細的描述。
1. 在客戶(hù)端和服務(wù)器中均使用javax.naming,將:
| import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; |
替換為:
| import javax.naming.*; |
2. 在客戶(hù)端和服務(wù)器中使用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 注冊表服務(wù)提供程序
RMI 注冊表服務(wù)提供程序允許JNDI 應用程序訪(fǎng)問(wèn)使用RMI注冊表注冊的遠程對象。已知注冊表所在的位置之后,提供程序使用綁定為注冊在注冊表中的對象創(chuàng )建一個(gè)命名上下文。接下來(lái),這個(gè)上下文可以被綁定到另一個(gè)JNDI可訪(fǎng)問(wèn)的命名空間中,比如LDAP。這項新功能包含了java.rmi.Naming 類(lèi)提供的功能。
這樣使用RMI的主要優(yōu)點(diǎn)是,客戶(hù)端不再需要知道RMI注冊表運行之處的主機名和端口號;它與位置無(wú)關(guān)。
下面的代碼片斷說(shuō)明了如何將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 服務(wù)提供程序
DNS服務(wù)提供程序使得基于JNDI的應用程序能夠訪(fǎng)問(wèn)存儲在DNS中的信息。DNS服務(wù)提供程序將DNS命名空間呈現為JNDI 目錄上下文的一棵樹(shù),而將DNS 資源記錄呈現為JNDI 屬性。
示例代碼 4 演示了如何使用DNS 服務(wù)提供程序檢索環(huán)境和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); } } } |
在您運行這個(gè)應用程序之前,確保您指定了DNS服務(wù)器的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平臺的標準服務(wù)API之一。包含它的目的是為應用程序組件提供一個(gè)標準的API,用于引用資源和其他應用程序組件。J2EE還定義了一種標準的命名策略(邏輯和真實(shí)的名稱(chēng)),以和 JNDI一起使用,這樣就可以采用一種與部署環(huán)境無(wú)關(guān)的方式編寫(xiě)應用程序。您可以引用 J2EE服務(wù),具體方法是根據其邏輯名稱(chēng)在目錄中查找它們。為了實(shí)現這一點(diǎn),每個(gè)符合 J2EE規范的系統均提供了一個(gè)稱(chēng)為環(huán)境的JNDI 服務(wù),該環(huán)境包括:
注意:在這里,我只討論環(huán)境變量。想要了解EJB引用和資源工廠(chǎng)引用方面的更多信息,請參見(jiàn)這篇文章 。
環(huán)境變量
應用程序組件的命名環(huán)境允許您定制應用程序組件,而不需要訪(fǎng)問(wèn)或修改組件的源代碼。每個(gè)應用程序組件定義它自己的環(huán)境入口的集合。同一個(gè)容器中,一個(gè)應用程序組件的所有實(shí)例共享同一個(gè)入口。注意,不允許應用程序組件實(shí)例在運行時(shí)修改環(huán)境。
聲明環(huán)境變量
應用程序組件提供程序必須聲明從應用程序的組件代碼訪(fǎng)問(wèn)的所有環(huán)境入口。它們是在部署描述器(例如Tomcat中的web.xml)中通過(guò)使用<env-entry> 標簽來(lái)聲明的. <env-entry> 標簽的元素有:
示例代碼 5中的例子給出了兩個(gè)環(huán)境入口的聲明。要為一個(gè)新環(huán)境指定一個(gè)聲明,您只要把它添加給您的web應用程序描述器 (web.xml) 即可。
示例代碼 5: 聲明環(huán)境變量
| <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> |
每個(gè)<env-entry>標簽描述一個(gè)環(huán)境入口。所以在這個(gè)例子中,定義了兩個(gè)環(huán)境入口。第一個(gè)叫做greetings, 是 String 類(lèi)型,初始的默認值為: Welcome to the Inventory Control System。第二個(gè)入口叫做 inventory/max,是 Integer類(lèi)型,初始的默認值為 27。
現在,應用程序組件實(shí)例可以使用JNDI定位環(huán)境入口. 它使用不帶參數的構造器創(chuàng )建了一個(gè)javax.naming.InitialContext 對象。接著(zhù),它通過(guò)InitialContext查找命名環(huán)境,所使用的是以java:comp/env打頭的JNDI URL。示例代碼 6 說(shuō)明了一個(gè)應用程序組件如何訪(fǎng)問(wèn)它的環(huán)境入口。
示例代碼 6: 訪(fǎng)問(wèn)環(huán)境入口
| // 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 |
注意,應用程序組件還可以使用如下的完整路徑名查找環(huán)境入口:
| javax.naming.Context ctx = new javax.naming.InitialContext(); String str = (String) ctx.lookup( "java:comp/env/greetings"); |
這段代碼片斷可以用在一個(gè) JSP 頁(yè)面中,如 示例代碼 7所示:
示例代碼 7: 從一個(gè) JSP頁(yè)面訪(fǎng)問(wèn)環(huán)境入口
| <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> |
結束語(yǔ)
JNDI是使用命名/目錄服務(wù)增強您的網(wǎng)絡(luò )應用程序的一組 API。本文通篇給出的例子演示了,開(kāi)始使用JNDI開(kāi)發(fā)基于目錄的應用程序是一件多么簡(jiǎn)單的事情。這些例子還演示了 如何使用相同的API訪(fǎng)問(wèn)不同的命名/目錄服務(wù)。開(kāi)發(fā)人員不必學(xué)習不同的 API。在某些情況下,比如在 RMI 和 CORBA應用程序中, JNDI 允許您將命名服務(wù)的選擇延至部署階段。
對JNDI 未來(lái)的期望有: 與標準的Java SASL API (JSR-28)結合,支持國際化的域名,而且支持安全的 DNS 。
要開(kāi)始學(xué)習和使用JNDI 和 LDAP,下載Sun ONE Directory Server的試用版,它可以用于各種平臺和各種語(yǔ)言。
聯(lián)系客服