java命名和目录接口(Java Naming and Directory Interface ,JNDI)是用于从Java应用程序中访问名称和目录服务的一组API。命名服务即将名称与对象相关联,以便能通过相应名称访问这些对象。而目录服务即其对象具有属性及名称的命名服务。
命名或目录服务答应您集中治理共享信息的存储,这在网络应用程序中很重要,因为它可以使这类应用程序更加一致和易于治理。例如,可以将打印机配置存储在目录服务中,这样所有与打印机相关的应用程序都能够使用它。
本文是一份代码密集型的快速入门指南,让您开始了解和使用JNDI。它:
提供对JNDI的综述。
描述JNDI的特性。
提供使用JNDI开发应用程序过程中的体验。
说明如何使用JNDI访问 LDAP 服务器,比如Sun Java System 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所示。
注重,关于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一起发行。
要通过来自命名或目录服务的名称检索或解析(查找)一个对象,使用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(Co