JNDI--Java命名和目录服务接口
JNDI是Java EE中的核心技术之一,它允许组件定位其他组件和资源,提供了企业级应用所需要的资源和外部信息的注册、存储以及获取组件等功能,许多J2EE组件的使用需要JNDI 的支持
在JMS、JMail、JDBC、EJB等技术中,大量应用这种技术
命名服务:将名字和对象绑定
目录服务:在将名字和对象关联的基础上,可以得到对象的属性
JNDI被分成了一下五个软件包:
javax.naming
javax.naming.directory
javax.naming.event
javax.naming.ldap
javax.naming.spi
Javax.naming: 包含用于访问命名服务的类和接口。例如,它定义了Context接口,该接口是执行查找时命名服务的入口点。
javax.naming.directory:扩展命名包以提供用于访问目录服务的类和接口。例如,它增加了新的属性类,提供代表一个目录上下文的DirContext 接口,并且定义了用于检查和更新与目录对象相关的属性的方法。
javax.naming.event: 当访问命名和目录服务时,为事件通知提供支持。例如,它定义了一个NamingEvent类,用于表示由命名/目录服务生成的事件,以及一个监视NamingEvent类的, NamingListener 接口。
javax.naming.ldap: 这个包为LDAP 版本 3 扩展操作和空间提供特定的支持,而普通的javax.naming.directory 包没有提供这些支持。
javax.naming.spi: 这个包提供方法以动态插入对通过javax.naming及其相关包访问命名和目录服务的支持。只有那些对创建服务提供程序有着浓厚兴趣的开发人员才应该对这个包感兴趣。
命名和目录概念
Names:为了在一个命名系统中查找一个对象,需要提供对象的名字,由命名系统来定义名称需要遵循的语法。
binding:JNDI命名服务有一组将名称与对象关联在一起的绑定
Context:一个上下文是一个名称-对象绑定的集合(可包含子上下文)。
Naming Systems:命名系统是连接在一起的相同类型上下文的集合,同时提供共同的操作集。
Namespaces:名称空间是命名系统中名称的集合。
在JNDI中,上下文是使用javax.naming.Context 接口来表示的,而这个接口也正是与命名服务进行交互的主要接口。
Context 接口中的每个命名方法都有两种重载的形式:
lookup(String name): 接受一个字符串名称。
lookup(javax.naming.Name): 接受一个结构化的名称,
InitialContext 是一个实现了 Context接口的类。使用这个类作为您到命名服务的入口点 。
创建一个InitialContext 对象
构造器需要采用一组属性,形式为java.util.Hashtable 或其子类之一
比如:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.fscontext.RefFSContext");
Context contxt = new InitialContext(env);
INITIAL_CONTEXT_FACTORY 指定JNDI服务提供程序中工厂类的名称。该工厂负责为其服务创建一个合适的InitialContext 对象。
名称 服务提供程序工厂
文件系统 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
要通过来自命名或目录服务的名称检索或解析(查找)一个对象,使用Context: Object obj = contxt.lookup(name)的lookup方法。lookup 方法返回一个对象,该对象代表您想要查找的上下文的子上下文。
比如:Printer printer = (Printer)ctx.lookup(hello.txt”);
printer.printer();
应用程序在运行环境中查找的资源(如数据源、Servlet、EJB)和外部信息必须在命名服务中注册,否则无法找到
用一个有意义的文本名与对象关联。通过分布式系统的命名和目录服务能够对分布式系统中的资源(文件、分布式对象、服务)进行方便的访问和管理
为避免与JNDI名称空间中的其他企业资源的名称冲突,并且避免可移植性问题,J2EE应用程序中的所有名称应该以字符串java:comp/env开始
步骤
第一步:为初始化上下文选择服务提供者
您可以为初始化上下文指定服务提供者,创建一个环境变量集合(Hashtable),同时将服务提供者的名称加入其中。
如果您使用Sun的LDAP服务提供者,代码如下所示:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
要指定Sun的文件系统服务提供者,代码如下所示:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.fscontext.RefFSContextFactory");
第二步:提供初始化上下文需要的信息
不同目录的客户端可能需要提供不同的信息用来连接目录。例如,您需要指定服务器运行的机器以及识别目录中的用户。这些信息通过环境属性传递给服务提供者。JNDI指定服务提供者使用的一般环境参数。您的服务提供者文档会为需要提供的参数进行详细的说明。
LDAP提供者需要程序提供LDAP服务器的位置,以及认证信息。要提供这些信息,需要如下代码:
env.put(Context.PROVIDER_URL, "ldap://ldap.wiz.com:389");
env.put(Context.SECURITY_PRINCIPAL, "joeuser");
env.put(Context.SECURITY_CREDENTIALS, "joepassword");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
如果您使用不同设置的目录,需要设置相应的环境属性。
第三步:创建初始化上下文
您已经创建了初始化上下文。为了做到这一点,您将之前创建的环境属性放到InitialContext构造函数中:
Context ctx = new InitialContext(env);
现在您有了一个上下文对象的引用,可以开始访问命名服务了。
为了完成目录操作,需要使用InitialDirContext。为了做到这一点,使用它的一个构造函数:
DirContext ctx = new InitialDirContext(env);
查询对象
要从命名服务中查询对象,使用Context.lookup()方法并且传入您想取得的对象名。假设命名服务中有一个对象的名称是cn=Rosanna Lee,ou=People。要取得这个对象,您只需要编写:
LdapContext ctx = (LdapContext) ctx.lookup("cn=Rosanna Lee,ou=People");
列举上下文
Context.List()方法
Context.list()返回NameClassPair的枚举。每个NameClassPair包含对象名和对象类型名。
如:NamingEnumeration list = ctx.list("ou=People");
while (list.hasMore()) {
NameClassPair nc = (NameClassPair)list.next();
System.out.println(nc);
}
Context.listBindings()方法
Context.listBindings()方法返回绑定的枚举。绑定是NameClassPair的子类。绑定不止包含对象名和对象类名,还包含对象。
NamingEnumeration bindings = ctx.listBindings("ou=People");
while (bindings.hasMore()) {
Binding bd = (Binding)bindings.next();
System.out.println(bd.getName() + ": " + bd.getObject());
}
为什么使用两个不同的方法?
list()为浏览类型的应用程序准备,只返回上下文中对象的名字
listBindings()为需要在上下文对象中进行操作的应用程序准备。
Context接口包含在上下文中添加、替换、删除绑定的方法。
添加或修改绑定
rebind()用来添加或替换绑定。
删除绑定
使用unbind()。
重命名
rename()对上下文中的对象进行重命名
创建和销毁子上下文
要创建命名上下文
Context createSubcontext(Name name)和Context createSubcontext(String name)
创建有属性的上下文
createSubcontext(Name name, Attributes attrs) createSubcontext(String name, Attributes attrs)
如:Attributes attrs = new BasicAttributes(true); // case-ignore
Attribute objclass = new BasicAttribute("objectclass");//构造一个不带值无序属性的新实例
objclass.add("top");//向此属性添加新值
objclass.add("organizationalUnit");//向此属性添加新值
attrs.put(objclass);//向属性集中添加新属性
Context result = ctx.createSubcontext("NewOu", attrs);
这个例子创建了名称为"ou=NewO"的上下文,并且有属性"objectclass","top"和"organizationalUnit"为其中"objectclass"有两个值。
销毁上下文
destroySubcontext()
属性名
属性由属性标识符和一组属性值组成。属性表示符叫属性名,是表示属性的字符串。
属性值是属性的内容,它的类型不一定是字符串。当您想要指定获取、搜索、或修改指定属性时,使用属性名。
当使用属性名时,您需要知道特定目录服务器的特性。
属性类型
在诸如LDAP之类的目录中,属性名表示了属性的类型,通常叫做属性类型名。例如,属性名"cn"同时叫做属性类型名。属性类型定义了属性值的语法,是否允许多值,相等性,以及对属性值执行比较和排序时的排序规则,
属性子类
一些目录实现支持目录子类型,就是服务器允许属性类型使用其他属性类型定义。例如,“name”属性可能是所有name相关属性的超类型:“commonName”是name的子类
属性名同义词
一些目录实现支持属性名的同义词。例如,“cn”可能是“commonName”的同义词。所以请求"cn"属性可能返回"commonName"属性。
读取属性
为了从目录中读取对象的属性,使用DirContext.getAttributes()并且将您想读取的属性名称传递进去就可以了。
假设在命名服务中的一个对象的名称是“cn=Ted Geisel, ou=People”。要获取对象的属性要使用如下代码:
Attributes answer = ctx.getAttributes("cn=Ted Geisel, ou=People");
返回选中属性
为了读取选中子集的属性,您需要提供想要获取的属性标识符的数组。
String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};
Attributes answer = ctx.getAttributes("cn=Ted Geisel, ou=People", attrIDs);
修改属性
DirContext接口包含修改目录中对象的属性和属性值的方法。
常见问题解答
1. 得到NoInitialContextException
原因:您没有指定使用的初始化上下文。特别的,Context.INITIAL_CONTEXT_FACTORY环境变量没有设置成为初始化上下文的工厂类名。后者,找不到Context.INITIAL_CONTEXT_FACTORY配置的可得到的服务提供者。
解决方案:将环境变量Context.INITIAL_CONTEXT_FACTORY设置为您使用的初始化上下文类名。
如果属性已经设置,那么确认类名没有输入错误,并且类在您的程序中可见(或者在classpath中或者在JRE的jre/lib/ext目录下)。Java平台包含服务提供者有LDAP,COS命名,DNS以及RMI注册。所有其他的服务提供者必须安装并且添加到执行环境中。
2. 得到CommunicationException异常,表示“连接被拒绝”。
原因:Context.PROVIDER_URL环境参数表示的服务器和端口没有提供访问。可能有人禁用或关闭了服务。或者输入了错误的服务器名称或端口号。
解决方案:检查端口上确实运行了服务,如果需要就重启服务器。这种检查依赖于您使用的LDAP服务器。通常,可以使用管理控制台或工具管理服务器。您可以使用工具确认服务器的状态。