Java命名和目录接口(JavaNamingandDirectoryInterface,JNDI)是用于从Java应用程序中访问名称和目录服务的一组API。命名服务即将名称与对象相关联,以便能通过相应名称访问这些对象。而目录服务即其对象具有属性及名称的命名服务。命名或目录服务允许您集中管理共享信息的存储,这在网络应用程序中很重要,因为它可以使这类应用程序更加一致和易于管理。例如,可以将打印机配置存储在目录服务中,这样所有与打印机相关的应用程序都能够使用它。本文是一份代码密集型的快速入门指南,让您开始了解和使用JNDI。它:提供对JNDI的综述。描述JNDI的特性。提供使用JNDI开发应用程序过程中的体验。说明如何使用JNDI访问LDAP服务器,比如SunONEDirectoryServer。说明如何使用JNDI访问J2EE服务。提供示例代码,您可以对其进行修改,以用于您自己的应用程序。JNDI综述我们所有人每天都在不自知的情况下使用命名服务。例如,当您在浏览器中输入URL时,域名系统(DomainNameSystem,DNS)将这个以符号表示的URL转换为一个通信标识符(IP地址)。在命名系统中,对象的范围可以从位于DNS记录中的名称变动到应用程序服务器中的企业JavaBeans组件(EnterpriseJavaBeansComponents,EJBs),还可以到轻量级目录访问协议(LightweightDirectoryAccessProtocol,LDAP)中的用户配置文件。目录服务是命名服务的自然扩展。二者的关键区别在于,目录服务允许属性(比如用户的电子邮件地址)与对象相关联,而命名服务则不然。这样,使用目录服务时,您可以基于对象的属性来搜索它们。JNDI允许您访问文件系统中的文件,定位远程RMI注册表中的对象,访问诸如LDAP这样的目录服务,并定位网络上的EJB。很多应用程序选择使用JNDI都可以收到良好的效果,比如LDAP客户端、应用程序启动器、类浏览器、网络管理实用工具,或者甚至是地址簿。JNDI架构JNDI架构提供了一个标准的、与命名系统无关的API,这个API构建在特定于命名系统的驱动程序之上。这一层帮助把应用程序和实际的数据源隔离开来,因此无论应用程序是访问LDAP、RMI、DNS还是其他的目录服务,这都没有关系。换句话说,JNDI与任何特定的目录服务实现无关,您可以使用任何目录,只要您拥有相应的服务提供程序接口(或驱动程序)即可,如图1所示。图1:JNDI架构注意,关于JNDI有一点很重要,即它同时提供应用程序编程接口(ApplicationProgrammingInterface,API)和服务提供程序接口(ServiceProviderInterface,SPI)。这样做的实际意义在于,对于您的与命名或目录服务交互的应用程序来说,必须存在用于该服务的一个JNDI服务提供程序,这便是JNDISPI发挥作用的舞台。一个服务提供程序基本上就是一组类,这些类针对特定的命名和目录服务实现了各种JNDI接口——这与JDBC驱动程序针对特定的数据系统实现各种JDBC接口极为相似。作为一名应用程序开发人员,您不需要担心JNDISPI.。您只需确保,您为每个想使用的命名或目录服务提供了一个服务提供程序。J2SE和JNDIJNDI被包含在Java2SDK1.3及其更新版本中。它还可以用作JDK1.1和1.2的一个标准扩展。Java2SDK1.4.x的最新版本进行了改进,将以下命名/目录服务提供程序包括进来:轻量级目录访问协议(LightweightDirectoryAccessProtocol,LDAP)服务提供程序。公共对象请求代理架构(CommonObjectRequestBrokerArchitecture,CORBA)公共对象服务(CommonObjectServices,COS)命名服务提供程序。Java远程方法调用(RemoteMethodInvocation,RMI)注册表服务提供程序。域名系统(DomainNameSystem,DNS)服务提供程序。有关服务提供程序的更多内容在这里可以下载一系列服务提供程序。Windows注册表JNDI提供程序(来自cogentlogic.com)可能会引起您特别的兴趣,因为它允许您访问WindowsXP/2000/NT/Me/9x上的注册表。此外,还可以下载JNDI/LDAPBootsterPack。这个增强补丁包含对流行的LDAP控件和扩展的支持。它代替了与LDAP1.2.1服务提供程序捆绑在一起的增强补丁。参见ControlsandExtensions以获得更多信息。另一个要考察的有趣的服务提供程序是Sun的DirectoryServicesMarkupLanguage(DSML)v2.0提供程序。DSML的目标是将目录服务与XML连接起来JNDIAPIJNDIAPI包括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(Stringname):接受一个字符串名称。lookup(javax.naming.Name):接受一个结构化的名称,比如CompositeName(一个跨越多个命名系统的名称)或CompondName(一个位于单个命名系统中的名称);二者均实现了Name接口。下面是一个复合名称的例子:cn=mydir,cn=QMahmoud,ou=People,还有一个组合名称的例子:cn=mydir,cn=QMahmoud,ou=People/myfiles/max.txt(这里的myfiles/max.txt是代表第二部分的一个文件名)。javax.naming.InitialContext是一个实现了Context接口的类。使用这个类作为您到命名服务的入口点。要创建一个InitialContext对象,构造器需要采用一组属性,形式为java.util.Hashtable或其子类之一,比如Properties.。下面是一个例子:Hashtableenv=newHashtable();//selectaserviceproviderfactoryenv.put(Context.INITIAL_CONTEXT_FACTORY,com.sun.jndi.fscontext.RefFSContext);//createtheinitialcontextContextcontxt=newInitialContext(env);INITIAL_CONTEXT_FACTORY指定JNDI服务提供程序中工厂类的名称。该工厂负责为其服务创建一个合适的InitialContext对象。在上面的代码片断中,指定了用于文件系统服务提供程序的一个工厂类。表1列出了用于所支持的服务提供程序的工厂类。注意,用于文件系统服务提供程序的工厂类需要从SunMicrosystems单独下载,它并没有与J2SE1.4.x一起发行。表1:Context.INITIAL_CONTEXT_FACTORY的值名称服务提供程序工厂文件系统com.sun.jndi.fscontext.RefFSContextFactoryLDAPcom.sun.jndi.ldap.LdapCtxFactoryRMIcom.sun.jndi.rmi.registry.RegistryContextFactoryCORBAcom.sun.jndi.cosnaming.CNCtxFactoryDNScom.sun.jndi.dns.DnsContextFactory要通过来自命名或目录服务的名称检索或解析(查找)一个对象,使用Context:Objectobj=contxt.lookup(name)的lookup方法。lookup方法返回一个对象,该对象代表您想要查找的上下文的子上下文。一个命名的例子现在,让我们看一看一个使用命名服务的例子。在这个例子中,我们编写了一个简单的程序,用于查找一个其名称被当作命令行参数传入的对象。在这里,我们将使用一个用于文件系统的服务提供程序,而且因此,我们提供作为参数的名称必须是一个文件名。示例代码1中给出了相应代码。示例代码1:Resolve.javaimportjavax.naming.Context;importjavax.naming.InitialContext;importjavax.naming.NamingException;importjava.util.Hashtable;publicclassResolve{publicstaticvoidmain(Stringargv[]){//Theusershouldprovideafiletolookupif(argv.length!=1){System.err.println(Usage:javaResolve);System.exit(-1);}Stringname=argv[0];//HereweusethefilesystemserviceproviderHashtableenv=newHashtable();env.put(Context.INITIAL_CONTEXT_FACTORY,com.sun.jndi.fscontext.RefFSContextFactory);try{//CreatetheinitialcontextContextctx=newInitialContext(env);//LookupanobjectObjectobj=ctx.lookup(name);//PrintitoutSystem.out.println(name+isboundto:+obj);//Closethecontextctx.close();}catch(NamingExceptione){System.err.println(Problemlookingup+name+:+e);}}}在这里,我假定您使用的是Java2SDK1.4.x,它附带有几个服务提供程序(上面已经列出)。这个应用程序