jndi step by step
(1) jndi 介绍
你真的十分了解JNDI吗?真的知道命名服务和目录服务的区别吗?
真的知道为什么有的时候 new InitContext() 会出错?原因是什么?
JNDI是J2EE的重要组成部分,让我们来好好学习一下Sun的JNDI教程吧,
把这些疑惑一一解开。
这些资料都来自Sun的JNDI教程,我会每天坚持翻译一点,但是不会全部翻译的,例如如何操作LDAP部分。
有的地方原文档有些啰嗦,我就改成简单的表达方式了。如果有翻译错的地方,请指证出来,我会修改。
1、命名服务
“命名”是计算机系统中最基本的一个部分。人们给予实物一个名字,然后通过这个名字找到实物。例如,当你发送
电子邮件的时候,你必须知道收件人的地址;当你在文件系统里查找一个文件的时候,你必须知道文件的名字。
命名服务就是这样的一个东西,它可以让你根据名字来找到一个实物。
命名服务主要的功能就是能有好的把名字和实物映射到一起,例如上面提过的邮件地址。例如,我们所熟悉的DNS系统,
它就会把 www.sun.com 映射成 ip地址192.9.48.5。再例如一个文件系统,一个 c:\bin\autoexec.bat ,就可以让程序访问这个
文件。这两个例子都说明命名系统其实是广泛存在和使用的---从 Internet 到一个单机的系统。
1.1 名字
为了在一个命名系统里找到某一个对象,你必须提供这个对象的名字才可以。这个命名系统决定名字的格式因该是什么
样子的。而这种格式,就被称为命名方式。
例如,Unix 文件系统的命名方式是相对跟目录的文件名,每一级目录都必须以“/”来分隔开来。例如:/usr/hello,就
表示一个在 /usr 目录里的、名字叫 hello 的文件。
DNS系统的命名方式,是不同的部分用“.”来分隔开来。因此,这个DNS名字 “sales.Wiz.com”,就表示了一个DNS项,名
字是 “sales”,但是请记住它是相对“Wiz.com”来说的。同样,“Wiz”是相对“com”的一个名字。
LDAP的命名习惯则是从右到左,由“,”分隔开来。因此这个LDAP名字“cn=Rosanna Lee, o=Sun, c=US”,就命名了一个
名字是“cn=Rosanna Lee”的项,当然,它是相对“o=Sun, c=US”来说的。同样,“o=Sun”是相对“c=US”的一个名字。而且LDAP还有
一个规则,就是每个名字,必须是“名字/数值”对应的,而且用“=”来分开。
1.2 绑定(Binding)
把一个名字和一个实际的对象联系在一起,就称为绑定。例如,一个文件名字和一个文件绑定在一起,一个域名和一个
IP地址绑定在一起,一个LDAP名字和一个LDAP项绑定在一起。
1.3 引用和地址
有些对象是不能直接保存在命名系统里的,也就是说,我们不能把某些对象复制到命名系统里。但是,命名系统可以保存
一个指向对象的引用--就好像一个指针。一个引用只是告诉命名系统如何去找到一个被引用的对象,一个引用里不会包含很多的
信息,因为你可以从对象那里得到详细的信息。
例如,一个“飞机”可能保存这样一些信息:乘客信息,驾驶员信息,飞行计划等等。但是,一个对这个“飞机”的引用,
可能只包含航班号和到达时间。
对于一个文件对象的引用来说,可能只包含文件的名字。
对于一个打印机对象的引用来说,可能只包含打印机的位置和所使用的通信协议。
尽管对于一个对象的引用中可以包含任意的信息,但是我们最好还是确定一些该如何才能找到一个对象--这就是所谓的
地址了。
1.4 内容上下文(Context)
内容上下文其实就是一系列的“名字-对象”的绑定。每一个内容上下文都有对应的命名规则。一个内容上下文都会提供一个
lookup 方法去查找一个对象,或许还会提供一些其它的方法,绑定新的“名字-对象”,解除一个绑定,或者列出全部的“名字-对象”
绑定。如果一个内容上下文里的某个“名字”对应的对象还是一个内容上下文,那这个“名字”对应的内容上下文可以被称为“子内容
上下文”(SubContext)。
例如,在Unix 系统里,“/usr”就是一个内容上下文,那么“/usr/bin”则就是子内容上下文。在域名上面,“com”是一个内容
上下文,那么对于“sun.com”来说,“sum”就是“com”的子内容上下文。
1.5 命名系统和命名空间
一个命名系统就是一系列具有相同命名方式并且提供一些相同操作的系统。例如,DNS就是一个命名系统,LDAP也是一个命名
系统。
一个命名系统提供了命名服务,和基于名字的一些操作。命名服务通过它的接口来访问。例如,DNS提供了映射域名和IP的
命名服务,一个文件系统提供了文件名字映射文件的服务。
命名空间就是一系列的命名系统的集合。例如,unix 系统的命名空间是由全部的文件和目录所组成。
2、目录服务
许多的命名服务被扩展成了目录服务。目录服务不仅用名字和对象作为联系,而且使用属性和对象进行联系。因此,你不仅
可以通过名字来找到一个对象,你还可以通过属性来找到一个对象。
电话公司的目录服务就是一个很好的例子。电话公司的系统把一个用户的名字和电话号码、地址联系在一起。一个目录服务
系统就像电话公司的一样--电话号码,地址,其实都是一个对象的属性。
一个目录对象可以是一个打印机,一个人,一个电脑,甚至是一个网络。一个目录对象会包含它所必需的属性。
2.1 属性
一个目录对象可以具有属性,例如一个打印机可以具有 型号,颜色等属性。一个用户对象可以具有电话号码,邮件地址等
属性。
属性包含一个属性名称和许多的属性值。
目录服务包含了一系列的目录对象,目录服务提供了许多方法来,例如创建,删除,等等。
2.2 查询和过滤
你可以通过给目录服务系统提供一个名字来查询一个目录对象。当然了,对于一些目录服务系统,例如LDAP,比较主张使用
查询语句来得到目录对象。使用查询语句查询对象的时候,你不仅可以指定一个目录的名字,还可以指定目录的属性,甚至是可以使用
一些查询表达式。这种使用表达式的方式,就是过滤查询了。这种方式有时候也被称为“反向查询”或者“基于内容的”查询。
例如,你可以从目录系统里查询所有年龄大于40岁的用户;你也可以查询所有IP地址以“192.113.50”开头的机器。
2.3 结合命名和目录服务
目录服务通常会以层次的方式组织目录对象。例如,LDAP把所有对象组织成一个树的形式,被称为 Directory Information Tree
(DIT)。例如,一个“组织”对象,可能会包含一个“组”对象,而一个“组”对象可能会包含一个“人”对象。
当目录对象以这种方式组织在一起的时候,除了可以被当作属性的“容器”以外,它们也可以被看成是命名服务对象了。
更简单点说,命名服务提供了一种层次的服务,而目录服务提供了一种属性的服务。我们一定要区分它们的概念。
从实际应用来说,我们可能只使用命名服务,而可以不必使用目录服务。但是如果我们使用了目录服务,那么一般都是要同时
使用命名服务的。
3、具有目录服务功能的Java 应用
目录服务是计算机网络的一个重要组成部分。通过使用目录,你可以简化应用和对应用的管理。随着java应用的越来越广泛,
掌握访问目录服务的能力就很必要了。
3.1 目录服务的传统使用方式
一个具有目录服务功能的应用,要么使用了命名服务,要么使用了目录服务。具有目录服务功能的应用和applets,和其他应
用一样,可以用传统的方式访问目录服务,也就是说,可以保存或者查询目录对象。一个Java邮件客户端,可以把一个目录服务作为
邮件地址本,可以从中查询邮件地址;一个Java邮件传输的代理程序,可以从中得到路由的信息;一个日历程序,可以从中得到用户的
设置信息。
各种应用程序可以通过目录服务得到公用的基础信息。这种共享方式,可以让应用跨系统部署,甚至是跨网络部署,并且让
这些部署的耦合更加清晰,而且便于管理。例如,打印机配置和邮件路由信息都可以保存在目录服务里,这样,这些配置就可以被所有
需要打印机和邮件路由的信息的应用所调用。
3.2 把对象保存在目录服务里
我们不仅可以按照传统的方式使用目录服务,Java应用也可以把目录服务作为一个对象的仓库来使用。例如,一个应用可以
从目录服务里得到一个打印机对象,然后发送一些信息给打印机对象让它打印出来。
4、JNDI 架构图
5、先来看看 jndi 的两个例子吧!
命名服务的一个例子,这个例子使用文件系统作为例子。你可以不必完全理解例子的内容,因为
我们后面会仔细的讲。
目录服务的一个例子,这个例子使用LDAP系统作为例子。你可以不必完全理解例子的内容,因为
我们后面会仔细的讲。
(2) 准备条件
1、准备工作
1.1 需要的软件环境
现在我们都已经都使用java 1.4 版本了,或者更高的版本,所以,java的版本这就不详细说了。
服务提供商(Service Provider Software)。从之前的jndi架构图可以知道,针对不同的jndi服务,我们会需要不同的SPI,这个
SPI就是服务提供商(Service Provider Software)提供的接口。
在这个教程里,我们主要使用以下两个 SPI:
1) 文件系统的jndi SPI
2) LDAP的 jndi SPI
你可以在 http://java.sun.com/products/jndi/serviceproviders.html 找到相应的SPI,然后下载。
命名和目录服务器。在这个文档里,我们会以LDAP为例子来介绍,你可以在 http://www.openldap.org/ 下载免费的LDAP服务器。
1.2 初始化内容上下文(Initial Context)
在使用任何命名或者目录服务的操作之前,你都必须初始化内容上下文--这是一个起点,这是因为所有的操作,都必须基于一个
特定的内容上下文才可以。初始化内容上下文,你必须遵守以下的步骤:
1) 选择好 服务提供商(Service Provider Software)
2) 确定一些初始化的配置
3) 调用 InitialContext 这个构造函数。
选择好 服务提供商(Service Provider Software)。
你可以使用以下的方法来实现这个功能:
上面那个例子是LDAP的,你也可以使用文件系统的,看下面的例子:
当然有的SPI可能需要更多的配置信息,例如:
对于文件系统的SPI,可能有如下的配置:
那么如何初始化内容上下文?使用下面的方式:
为了访问目录服务,你应该换一个初始化方式:
这些配置你也可以放到属性配置文件( .properties )里,在高级应用中我们再讨论。
(3) 命名服务的操作
命名服务的操作
1、寻找一个对象
为了从命名服务中找到一个对象,你可以使用 Context.lookup() 方法,只要传递给它你要寻找的对象的名字就可以。例如,在当前的
命名服务中,有一个对象的名字是“report.txt”,为了找到这个对象,你可以使用
Object obj = ctx.lookup("report.txt");
lookup 返回的对象类型,依据你的实际情况来看。命名系统里可以保存各种类型的对象,在这个例子中,“report.txt”返回的对象
因该是文件系统的一个对象(java.io.File) ,你可以进行强制转换:
下面是这个例子的完整代码:
2、显示内容上下文列表
使用 Context.lookup() 方法,你可以得到指定的对象,实际上你还可以通过其他方法来得到内容上下文里的全部对象的列表。你可以
通过2个方法来实现这个目的,其中一个返回绑定(binding)的列表,另一个仅仅返回“名字--类名”成对的列表。
2.1、Context.List() 方法
Context.List() 方法返回一个 NameClassPair 类的对象的枚举,枚举中的每一个 NameClassPair 对象,都是由对象的名字和对象的类
名组成的。下面的代码片段,演示了“awt”目录的内容上下文的列表。
输出结果就是:
# java List
accessibility: javax.naming.Context
color: javax.naming.Context
datatransfer: javax.naming.Context
dnd: javax.naming.Context
event: javax.naming.Context
font: javax.naming.Context
geom: javax.naming.Context
im: javax.naming.Context
image: javax.naming.Context
peer: javax.naming.Context
print: javax.naming.Context
swing: javax.naming.Context
2.2、Context.listBindings() 方法
Context.listBindings() 方法返回一个 Bingding 类的对象的枚举,Binding 类是 NameClassPair 类的之类。一个 Binding 对象,不仅仅包含对象的名字,对象的类名,还包含一个对象。下慢的代码片段演示了这个方法的功能:
输出结果:
# java ListBindings
accessibility: com.sun.jndi.fscontext.RefFSContext@1dacd52e
color: com.sun.jndi.fscontext.RefFSContext@1dacd551
datatransfer: com.sun.jndi.fscontext.RefFSContext@1dacd584
dnd: com.sun.jndi.fscontext.RefFSContext@1dacd5b6
event: com.sun.jndi.fscontext.RefFSContext@1dacd5e8
font: com.sun.jndi.fscontext.RefFSContext@1dacd61b
geom: com.sun.jndi.fscontext.RefFSContext@1dacd64d
im: com.sun.jndi.fscontext.RefFSContext@1dacd62a
image: com.sun.jndi.fscontext.RefFSContext@1dacd65c
peer: com.sun.jndi.fscontext.RefFSContext@1dacd68f
print: com.sun.jndi.fscontext.RefFSContext@1dacd6c1
swing: com.sun.jndi.fscontext.RefFSContext@1dacd6f3
2.3、如何停止一个枚举的循环
NamingEnumeration 类可以以三种方式来终止循环:自然的,显示的,异常方式。
a) 当 NamingEnumeration.hasMore() 返回 false,枚举就自动终止了。
b) 可以使用 NamingEnumeration.close() 来强行终止一个循环,但是记得要释放其中的资源。
c) 如果你抛出一个 NamingException ,那么循环也会被终止。
不管枚举是如何被终止的,一旦它被终止了,那么就不要再使用这个变量。否则会发生不确定的错误。
2.4、为什么我们会需要这2个方法?
list() 方法是为浏览器风格的应用而准备的,因为这种应用一般仅仅要求现实对象的名字。例如,浏览器
会显示一个内容上下文里的全部对象的名字,然后让用户去选择,以进行下一步操作。一般这样的应用没必要
访问内容上下文里的具体对象。
listBindings() 方法是为一些会操作内容上下文里的对象的应用而准备的。例如,一个后台程序可能会查看
某一个目录里的全部文件的状态。或者一个打印机管理程序会重新启动全部的打印机。为了执行类似的操作,
应用程序必须得到内容上下文里的具体对象,因此,用这个方法就显得特别有效了。
这两个方法,你可以根据实际的情况来选择使用。
3、添加、修改、删除一个绑定(Binding)
Context 借口有一些针对binding的操作方法:adding, replacing, 和 removing。
3.1、增加一个binding
Context.bind() 方法可以给内容上下文增加一个binding,可以给传入两个参数:一个是对象的名字,另一个
是对象。
下面用代码演示了这个方法的使用:
如果你运行两次这个例子,那么会遇到一个错误:NameAlreadyBoundException。这是因为在内容上下文里
不能有重复名字的对象。如果你想运行两次,那么就需要使用 rebind() 方法。
3.2、增加或替换一个binding
rebind()方法是用来增加或者替换一个binding。参数和 bind() 方法一样,不同的是,如果内容上下文里
已经存在了同名的对象,那么就会用新的对象把旧的对象替换掉。
3.3、删除一个binding
你可以使用 unbind() 方法来删除一个绑定。
4、重新命名一个对象
你可以使用 Context.rename() 方法来重新命名一个对象。
5、创建和删除一个子内容上下文
Context 接口里有两个方法可以创建和删除子内容上下文。如果是对于文件系统来说,那就是创建和删除一个
子目录。
5.1、创建一个内容上下文
你可以使用 createSubcontext() 方法来创建一个子内容上下文。
下面是一个例子:
5.2、删除一个上下文
你可以使用 destroySubcontext() 来删除一个内容上下文。
下面是一个例子: