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