命名和目录操作
您可以使用JNDI执行以下操作:读取操作和更新命名空间的操作。本节介绍这两个操作:
l 查询对象
l 列出上下文内容
l 添加、覆盖和移除绑定
l 重命名对象
l 创建和销毁子上下文
配置
在命名和目录服务中执行操作之前,需要得到初始化上下文――命名空间的开始点。因为命名和目录服务的所有方法都相对于一些上下文执行。
为了得到初始化上下文,必须执行以下步骤:
1. 选择想要访问的访问提供者。
2. 指定需要的初始化上下文。
3. 调用InitialContext构造函数。
第一步:为初始化上下文选择服务提供者
您可以为初始化上下文指定服务提供者,创建一个环境变量集合(Hashtable),同时将服务提供者的名称加入其中。环境属性在JNDI教程中有详细的介绍。
如果您使用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教程中有详细描述。
第二步:提供初始化上下文需要的信息
不同目录的客户端可能需要提供不同的信息用来连接目录。例如,您需要指定服务器运行的机器以及识别目录中的用户。这些信息通过环境属性传递给服务提供者。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”); |
本教程中使用Sun的LDAP服务提供者。例子中假设服务器设置在本机,使用389端口,根辨别名是“o=JNDITutorial”,修改目录不需要认证。这些信息是设置环境所需要的。
env.put(Context.PROVIDER_URL, “ldap://localhost:389/o=JNDITutorial”); |
如果您使用不同设置的目录,需要设置相应的环境属性。您需要使用机器名称替换“localhost”。您可以在任何公共的目录服务器或在其他机器上的自己的服务器运行例子。您需要将“localhost”替换成那台机器的名字,将o=JNDITutorial替换成相应的命名上下文。
第三步:创建初始化上下文
您已经创建了初始化上下文。为了做到这一点,您将之前创建的环境属性放到InitialContext构造函数中:
Context ctx = new InitialContext(env); |
现在您有了一个上下文对象的引用,可以开始访问命名服务了。
为了完成目录操作,需要使用InitialDirContext。为了做到这一点,使用它的一个构造函数:
DirContext ctx = new InitialDirContext(env); |
这句话返回了用来进行目录操作的DirContext对象引用。
命名异常
JNDI包中的很多方法当抛出异常时,说明操作请求不能执行。一般情况下,您将看到可以抛出NamingException的方法使用try/catch进行包装。
try { Context ctx = new InitialContext(); Object obj = ctx.lookup(“somename”); } catch (NamingException e) { // Handle the error System.err.println(e); } |
异常类结构
JNDI有丰富的异常结构,所有异常都从NamingException类中继承。异常类名都是自解释的,在下文中进行列举。
如果要处理特定的NamingException子类,需要分别catch子类。例如,以下代码特别的对待AuthenticationException及其子类。
try { Context ctx = new InitialContext(); Object obj = ctx.lookup(“somename”); } catch (AuthenticationException e) { // attempt to reacquire the authentication information … } catch (NamingException e) { // Handle the error System.err.println(e); } |
枚举
诸如Context.list()和DirContext.search()这种操作返回NamingEnumeration。在这些情况下,如果出现错误并且没有返回结果,NamingException或它的子类会在方法请求时抛出。如果出现错误并且返回了部分结果,返回NamingEnumeration这样您可以取得这些结果。当所有结果都取出来后,再请求NamingEnumeration.hasMore()会导致抛出NamingException(或其子类)异常,表示出现错误。在这种情况下,枚举变得非法并且不能再请求其中任何方法。
例如,如果执行search()并且指定最多返回多少结果,那么search()最多返回n个结果。如果结果超过n个,那么当第n+1次请求NamingEnumeration.hasMore()时,抛出SizeLimitExceededException。请参见本节中的有关limit的示例代码。
本手册中的例子
在本手册文件中的在线示例代码中,通常为了便于阅读省略了try/catch语句。通常,因为只有部分代码片段在这里展示,所以只展示直接表示概念的行。如果您查看本教程附带的源码文件,将看到try/catch语句的合适位置。
javax.naming包中异常在这里可以看到。
查询对象
要从命名服务中查询对象,使用Context.lookup()方法并且传入您想取得的对象名。假设命名服务中有一个对象的名称是cn=Rosanna Lee,ou=People。要取得这个对象,您只需要编写:
Object obj = ctx.lookup(“cn=Rosanna Lee,ou=People”); |
lookup()返回的对象类型依赖于命名服务以及对象关联的数据。命名服务可以包含许多不同类型的对象,同时在系统的不同部分查询的对象可能得到不同的类型。例如,“cn=Rosanna Lee,ou=People”绑定到上下文对象中(javax.naming.ldap.LdapContext)。您可以对lookup()方法的结果cast成需要的类。
例如,以下代码查询“cn=Rosanna Lee,ou=People”对象并且cast成LdapContext。
import javax.naming.ldap.LdapContext; … LdapContext ctx = (LdapContext) ctx.lookup(“cn=Rosanna Lee,ou=People”); |
完整的例子在Lookup.java文件中。
在Java SE 6中查询名称引入了两个新的静态方法:
l InitialContext.doLookup(Name name)
l InitialContext.doLookup(String name)
这些方法提供了不实例InitialContext查找对象的快捷方式。
列举上下文
代替Context.lookup()一次取得一个对象的方法,您可以在一个单一的操作中列举整个剩下文。列举上下文有两个方法:一个返回了绑定关系,另一个只返回名-对象类型名。
Context.List()方法
Context.list()返回NameClassPair的枚举。每个NameClassPair包含对象名和对象类型名。下列代码列举了“ou=People”目录的内容(例如,“ou=People”目录中找到的文件和目录)。
NamingEnumeration list = ctx.list(“ou=People”);
while (list.hasMore()) { NameClassPair nc = (NameClassPair)list.next(); System.out.println(nc); } |
返回值如下:
# java List cn=Jon Ruiz: javax.naming.directory.DirContext cn=Scott Seligman: javax.naming.directory.DirContext cn=Samuel Clemens: javax.naming.directory.DirContext cn=Rosanna Lee: javax.naming.directory.DirContext cn=Maxine Erlund: javax.naming.directory.DirContext cn=Niels Bohr: javax.naming.directory.DirContext cn=Uri Geller: javax.naming.directory.DirContext cn=Colleen Sullivan: javax.naming.directory.DirContext cn=Vinnie Ryan: javax.naming.directory.DirContext cn=Rod Serling: javax.naming.directory.DirContext cn=Jonathan Wood: javax.naming.directory.DirContext cn=Aravindan Ranganathan: javax.naming.directory.DirContext cn=Ian Anderson: javax.naming.directory.DirContext cn=Lao Tzu: javax.naming.directory.DirContext cn=Don Knuth: javax.naming.directory.DirContext cn=Roger Waters: javax.naming.directory.DirContext cn=Ben Dubin: javax.naming.directory.DirContext cn=Spuds Mackenzie: javax.naming.directory.DirContext cn=John Fowler: javax.naming.directory.DirContext cn=Londo Mollari: javax.naming.directory.DirContext cn=Ted Geisel: javax.naming.directory.DirContext |
Context.listBindings()方法
Context.listBindings()方法返回绑定的枚举。绑定是NameClassPair的子类。绑定不止包含对象名和对象类名,还包含对象。以下代码片段枚举了“ou=People”上下文,打印出每一个绑定名称和对象。
NamingEnumeration bindings = ctx.listBindings(“ou=People”);
while (bindings.hasMore()) { Binding bd = (Binding)bindings.next(); System.out.println(bd.getName() + “: ” + bd.getObject()); } |
返回的结果如下:
# java ListBindings cn=Jon Ruiz: com.sun.jndi.ldap.LdapCtx@1d4c61c cn=Scott Seligman: com.sun.jndi.ldap.LdapCtx@1a626f cn=Samuel Clemens: com.sun.jndi.ldap.LdapCtx@34a1fc cn=Rosanna Lee: com.sun.jndi.ldap.LdapCtx@176c74b cn=Maxine Erlund: com.sun.jndi.ldap.LdapCtx@11b9fb1 cn=Niels Bohr: com.sun.jndi.ldap.LdapCtx@913fe2 cn=Uri Geller: com.sun.jndi.ldap.LdapCtx@12558d6 cn=Colleen Sullivan: com.sun.jndi.ldap.LdapCtx@eb7859 cn=Vinnie Ryan: com.sun.jndi.ldap.LdapCtx@12a54f9 cn=Rod Serling: com.sun.jndi.ldap.LdapCtx@30e280 cn=Jonathan Wood: com.sun.jndi.ldap.LdapCtx@16672d6 cn=Aravindan Ranganathan: com.sun.jndi.ldap.LdapCtx@fd54d6 cn=Ian Anderson: com.sun.jndi.ldap.LdapCtx@1415de6 cn=Lao Tzu: com.sun.jndi.ldap.LdapCtx@7bd9f2 cn=Don Knuth: com.sun.jndi.ldap.LdapCtx@121cc40 cn=Roger Waters: com.sun.jndi.ldap.LdapCtx@443226 cn=Ben Dubin: com.sun.jndi.ldap.LdapCtx@1386000 cn=Spuds Mackenzie: com.sun.jndi.ldap.LdapCtx@26d4f1 cn=John Fowler: com.sun.jndi.ldap.LdapCtx@1662dc8 cn=Londo Mollari: com.sun.jndi.ldap.LdapCtx@147c5fc cn=Ted Geisel: com.sun.jndi.ldap.LdapCtx@3eca90 |
结束NamingEnumeration
NamingEnumeration可以通过三种方式终止:一般的,显式的,非显式的。
l 当NamingEnumeration.hasMore()返回false,枚举结束同时终止。
l 您可以在枚举终止前请求NamingEnumeration.close()方法显式的终止一个枚举。这样做提示底层实现释放任何和枚举有关的资源。
l 如果hasMore()或next()方法抛出任何异常,枚举立即终止。
不管如何终止枚举,枚举一旦被终止就不能再使用。在一个已终止的枚举中请求任何方法都会导致不确定的结果。
为什么使用两个不同的方法?
list()为浏览类型的应用程序准备,只返回上下文中对象的名字。例如,浏览器可能列出上下文中的名字,期待用户选择一个或多个显示的名称来进行后续操作。这些应用程序一般不需要访问上下文中所有的对象。
listBindings()为需要在上下文对象中进行操作的应用程序准备。例如,备份程序需要在文件目录中所有对象中执行“file stats”操作。或者,一个打印机管理员程序可能想要重启建筑物内的所有打印机。为了执行这些操作,需要得到上下文中的所有对象。因此,将对象作为枚举的一部分返回是权宜之计。
应用程序可以依据需要的信息类型选择list()或listBindings()。
添加、替换、或删除绑定
Context接口包含在上下文中添加、替换、删除绑定的方法。
添加绑定
Context.bind()为了向上下文中添加绑定,它以对象类型以及需要绑定的对象作为参数。
在继续前:本教程中的例子需要您对架构做额外的修改。您必须关闭LDAP服务器的架构检测或将符合本教程的架构添加到服务器中。这种工作一般由目录服务器管理员执行。请看课程。
// Create the object to be bound Fruit fruit = new Fruit(“orange”);
// Perform the bind ctx.bind(“cn=Favorite Fruit”, fruit); |
这个例子创建了一个Fruit类的对象同时在上下文ctx中将他绑定到名称“cn=Favorite Fruit”中。如果您随后在ctx中查询“cn=Favorite Fruit”,那么您将得到fruit兑现。注意,编译Fruit类需要FruitFactory类。
如果您运行这个例子两次,那么第二次会因为NameAlreadyBoundException异常失败。因为“cn=Favorite Fruit”已经绑定了。要第二次运行时不失败,需要使用rebind()。
添加或修改绑定
rebind()用来添加或替换绑定。它的参数列表和bind()一样,但如果名称已经绑定,那么首先会unbound然后再重新绑定新的对象。
// Create the object to be bound Fruit fruit = new Fruit(“lemon”);
// Perform the bind ctx.rebind(“cn=Favorite Fruit”, fruit); |
当您运行这个例子时,将会替换bind()例子中已经创建的绑定关系。
删除绑定
要删除绑定,使用unbind()。
// Remove the binding ctx.unbind(“cn=Favorite Fruit”); |
当这个例子运行时,删除bind()或unbind()创建的绑定关系。
重命名
您使用Context.rename()对上下文中的对象进行重命名。
// Rename to Scott S ctx.rename(“cn=Scott Seligman”, “cn=Scott S”); |
这个例子将绑定到“cn=Scott Seligman”的对象绑定到了“cn=Scott S”中。在验证对象被重命名后,程序再将其重命名回原来的名字(“cn=Scott Seligman”),如下所示:
// Rename back to Scott Seligman ctx.rename(“cn=Scott S”, “cn=Scott Seligman”); |
更多关于LDAP中重命名的例子请参考LDAP的高级注意。
创建和销毁子上下文
Context接口包含创建和销毁一个子上下文。子上下文是绑定到其他上下文的上下文。
这个例子使用一个有属性的对象,然后在目录中创建子上下文。您可以使用DirContext的方法将属性和对象在绑定或子上下文添加到名字空间时进行关联。例如,您可以创建Person对象,然后在为Person对象关联属性的同时将他绑定到命名空间中。命名等于没有任何属性。
createSubcontext()和bind()不同,他创建了一个新对象,例如一个要绑定到目录中的新上下文,但bind()在目录中绑定了给定的对象。
创建上下文
要创建命名上下文,您向createSubcontext()提供要创建上下文的名称。要创建有属性的上下文,向DirContext.createSubcontext()提供想要创建上下文的名称以及需要的属性。
在继续前:本教程中的例子需要您对架构做额外的修改。您必须关闭LDAP服务器的架构检测或将符合本教程的架构添加到服务器中。这种工作一般由目录服务器管理员执行。请看课程。
// Create attributes to be associated with the new context Attributes attrs = new BasicAttributes(true); // case-ignore Attribute objclass = new BasicAttribute(“objectclass”); objclass.add(“top”); objclass.add(“organizationalUnit”); attrs.put(objclass);
// Create the context Context result = ctx.createSubcontext(“NewOu”, attrs); |
这个例子创建了名称为“ou=NewO”的上下文,并且有属性“objectclass”,属性“top”和“organizationalUnit”,其中“objectclass”有两个值。
# java Create ou=Groups: javax.naming.directory.DirContext ou=People: javax.naming.directory.DirContext ou=NewOu: javax.naming.directory.DirContext |
这个例子创建了一个新的上下文,叫“NewOu”,是ctx的子上下文。
销毁上下文
要销毁上下文,需要向destroySubcontext()提供需要销毁上下文的名称。
// Destroy the context ctx.destroySubcontext(“NewOu”); |
这个例子在上下文ctx中删除上下文“NewOu”。
属性名
属性由属性标识符和一组属性值组成。属性表示符叫属性名,是表示属性的字符串。属性值是属性的内容,它的类型不一定是字符串。当您想要指定获取、搜索、或修改指定属性时,您使用属性名。名称同时在返回属性的操作中返回(例如,当您执行目录的读取或搜索操作时)。
当使用属性名时,您需要知道特定目录服务器的特性,所以您不会为结果惊奇。这些特定在下一子节中描述。
属性类型
在诸如LDAP之类的目录中,属性名表示了属性的类型,通常叫做属性类型名。例如,属性名“cn”同时叫做属性类型名。属性类型定义了属性值的语法,是否允许多值,相等性,以及对属性值执行比较和排序时的排序规则,
属性子类
一些目录实现支持目录子类型,就是服务器允许属性类型使用其他属性类型定义。例如,“name”属性可能是所有name相关属性的超类型:“commonName”是name的子类。对于支持这种特斯娜格的目录实现,访问“name”属性可能返回“commonName”属性。
当访问支持子类型的属性的目录时,要知道服务器可能返回和您请求不一致的类型。为了减少这种几率,使用最派生类。
属性名同义词
一些目录实现支持属性名的同义词。例如,“cn”可能是“commonName”的同义词。所以请求“cn”属性可能返回“commonName”属性。
当访问支持属性同义词的目录,您必须意识到服务器可能返回和您请求不同的属性名。要防止这种情况发生,使用官方的属性名代替使用同义词。官方的属性名是定义属性的属性名;同义词是是定义中引用官方属性名的名称。
语言参数选择
LDAP v3的扩展(RFC 2596)允许您和属性名一起指定语言编码。类似于子类属性,一个属性名可以表示多个不同的属性。例如“description”属性有两个不同的语言变体:
description: software description;lang-en: software products description;lang-de: Softwareprodukte |
对“description”属性的请求会返回所有三种属性。当访问支持这种特性的目录时,您必须意识到服务器可能返回和请求时不同的名称。
读取属性
为了从目录中读取对象的属性,使用DirContext.getAttributes()并且将您想读取的属性名称传递进去就可以了。假设在命名服务中的一个对象的名称是“cn=Ted Geisel, ou=People”。要获取对象的属性要使用如下代码:
Attributes answer = ctx.getAttributes(“cn=Ted Geisel, ou=People”); |
您可以按照如下方式打印应答的内容:
for (NamingEnumeration ae = answer.getAll(); ae.hasMore();) { Attribute attr = (Attribute)ae.next(); System.out.println(“attribute: ” + attr.getID()); /* Print each value */ for (NamingEnumeration e = attr.getAll(); e.hasMore(); System.out.println(“value: ” + e.next())) ; } |
输出如下:
# java GetattrsAll attribute: sn value: Geisel attribute: objectclass value: top value: person value: organizationalPerson value: inetOrgPerson attribute: jpegphoto value: [B@1dacd78b attribute: mail value: Ted.Geisel@JNDITutorial.com attribute: facsimiletelephonenumber value: +1 408 555 2329 attribute: telephonenumber value: +1 408 555 5252 attribute: cn value: Ted Geisel |
返回选中属性
为了读取选中子集的属性,您需要提供想要获取的属性标识符的数组。
// Specify the ids of the attributes to return String[] attrIDs = {“sn”, “telephonenumber”, “golfhandicap”, “mail”};
// Get the attributes requested Attributes answer = ctx.getAttributes(“cn=Ted Geisel, ou=People”, attrIDs); |
这个例子请求对象“cn=Ted Geisel, ou=People”的“sn”,“telephonenumber”,“golfhandicap”和“mail”属性,所以应答中返回这三个属性。
以下是输出结果:
# java Getattrs attribute: sn value: Geisel attribute: mail value: Ted.Geisel@JNDITutorial.com attribute: telephonenumber value: +1 408 555 5252 |
修改属性
DirContext接口包含修改目录中对象的属性和属性值的方法。
使用修改列表
修改对象属性的一个方法是提供修改列表(ModificationItem)。每一个ModificationItem包含数字常量表示修改的类型以及描述需要修改的属性。以下是修改的类型。
l ADD_ATTRIBUTE
l REPLACE_ATTRIBUTE
l REMOVE_ATTRIBUTE
修改以列表中提供的类型进行。或者所有的修改都执行,或者一个都不执行。
以下代码创建修改列表。它将“mail”属性值替换成geisel@wizards.com,为“telephonenumber”属性添加附加值,并且删除“jpegphoto”属性。
// Specify the changes to make ModificationItem[] mods = new ModificationItem[3];
// Replace the “mail” attribute with a new value mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(“mail”, “geisel@wizards.com”));
// Add an additional value to “telephonenumber” mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute(“telephonenumber”, “+1 555 555 5555”));
// Remove the “jpegphoto” attribute mods[2] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute(“jpegphoto”)); |
Windows活动目录:活动目录将“telephonenumber”属性定义为单值属性,这和RFC 2256不符。为了让这个例子在活动目录中执行,您必须或者使用其他属性代替“telephonenumber”或将DirContext.ADD_ATTRIBUTE改为DirContext.REPLACE_ATTRIBUTE。
在创建修改列表后,您可以按照如下方式提供给modifyAttributes():
// Perform the requested modifications on the named object ctx.modifyAttributes(name, mods); |
使用属性
可选的,您可以通过指定修改类型以及修改属性的方式进行修改。
例如,以下行使用orig中的name关联需要替换的属性:
ctx.modifyAttributes(name, DirContext.REPLACE_ATTRIBUTE, orig); |
其他属性的名称没有改变。
两种对于modifyAttributes()的使用在示例程序中都有。使用修改列表修改属性的程序在第二部分modifyAttributes()中恢复了原来的属性。
有属性的添加、删除绑定
命名的例子讨论如何使用bind()和unbind(),DirContext这两方法的重载版本。您使用DirContext的方法关联对象的属性,在绑定或子上下文时将她们添加到名字空间中。例如,您可能创建了Person对象,然后在为Person对象关联属性时将他绑定到名字空间中。
添加有属性的绑定
DirContext.bind()用来将属性绑定添加到上下文中。它的参数为需要绑定的对象名称, 以及属性集合。
// Create the object to be bound Fruit fruit = new Fruit(“orange”);
// Create attributes to be associated with the object Attributes attrs = new BasicAttributes(true); // case-ignore Attribute objclass = new BasicAttribute(“objectclass”); objclass.add(“top”); objclass.add(“organizationalUnit”); attrs.put(objclass);
// Perform bind ctx.bind(“ou=favorite, ou=Fruits”, fruit, attrs); |
这个例子创建了Fruit类的对象并且将他绑定到“ou=Fruits”上下文中,名称为“ou=favorite”。绑定有“objectclass”属性。如果接下来在ctx中查询“ou=favorite, ou=Fruits”,那么您将得到fruit对象。如果您想、得到“ou=favorite, ou=Fruits”的属性,您将得到刚才为对象添加的属性。以下是例子的输出:
# java Bind orange attribute: objectclass value: top value: organizationalUnit value: javaObject value: javaNamingReference attribute: javaclassname value: Fruit attribute: javafactory value: FruitFactory attribute: javareferenceaddress value: #0#fruit#orange attribute: ou value: favorite |
显示的多于属性使用来保存关于对象(fruit)的一些信息。这些多于信息随后会进行详细介绍。
如果您两次运行这个例子,那么第二次运行时将会失败并抛出NameAlreadyBoundException。这是因为“ou=favorite”已经绑定到上下文“ou=Fruits”中。如果要成功,需要使用rebind()。
替换有属性的绑定
DirContext.rebind()的作用是添加或修改绑定以及属性。它接受和bind()一样的参数。然而,使用rebind()时如果名称已经存在,那么将会首先Unbind然后再绑定新的对象和属性。
// Create the object to be bound Fruit fruit = new Fruit(“lemon”);
// Create attributes to be associated with the object Attributes attrs = new BasicAttributes(true); // case-ignore Attribute objclass = new BasicAttribute(“objectclass”); objclass.add(“top”); objclass.add(“organizationalUnit”); attrs.put(objclass);
// Perform bind ctx.rebind(“ou=favorite, ou=Fruits”, fruit, attrs); |
运行这个例子时,它替换了bind()例子中创建的绑定关系。
# java Rebind lemon attribute: objectclass value: top value: organizationalUnit value: javaObject value: javaNamingReference attribute: javaclassname value: Fruit attribute: javafactory value: FruitFactory attribute: javareferenceaddress value: #0#fruit#lemon attribute: ou value: favorite |
搜索
目录提供的最有用的好处就是黄页功能,或搜索服务。您可以组合一个包含条目属性的查询,然后提交查询到目录中。然后目录返回满足查询的条目列表。例如,您可以访问目录,查询出保龄球平均成绩大于200的条目,或所有姓以“Sch”开头的人。
DirContext接口提供查询条目的方法,这些方法很复杂也很强大。搜索目录的不同方面在以下章节中描述:
l 基本搜索
l 搜索过滤器
l 搜索控制
基本搜索
最简单的查询需需要您指定条目必须含有的属性集合以及进行查询的目标上下文。
以下代码创建了属性集合matchAttrs,其中有两个属性“sn”和“mail”。表名搜索条目必须有姓(sn)属性,其值为“Geisel”,以及“mail”属性可以是任意值。然后调用DirContext.search()查询上下文“ou=People”中和matchAttrs匹配的条目。
// Specify the attributes to match // Ask for objects that has a surname (“sn”) attribute with // the value “Geisel” and the “mail” attribute Attributes matchAttrs = new BasicAttributes(true); // ignore attribute name case matchAttrs.put(new BasicAttribute(“sn”, “Geisel”)); matchAttrs.put(new BasicAttribute(“mail”));
// Search for objects that have those matching attributes NamingEnumeration answer = ctx.search(“ou=People”, matchAttrs);
You can then print the results as follows. while (answer.hasMore()) { SearchResult sr = (SearchResult)answer.next(); System.out.println(“>>>” + sr.getName()); printAttrs(sr.getAttributes()); } |
printAttrs()方法和getAttributes()方法打印出属性集合类似。
返回结果如下:
# java SearchRetAll >>>cn=Ted Geisel attribute: sn value: Geisel attribute: objectclass value: top value: person value: organizationalPerson value: inetOrgPerson attribute: jpegphoto value: [B@1dacd78b attribute: mail value: Ted.Geisel@JNDITutorial.com attribute: facsimiletelephonenumber value: +1 408 555 2329 attribute: cn value: Ted Geisel attribute: telephonenumber value: +1 408 555 5252 |
返回选中属性
上一个例子返回满足指定查询条件条目的所有属性。您可以通过向search()传递属性标识符数组的方式选择想要包含在结果集中的属性。在创建matchAttrs只有,您应该创建属性标识符的数组,如下所示:
// Specify the ids of the attributes to return String[] attrIDs = {“sn”, “telephonenumber”, “golfhandicap”, “mail”};
// Search for objects that have those matching attributes NamingEnumeration answer = ctx.search(“ou=People”, matchAttrs, attrIDs); |
这个例子返回条目的属性“sn”,“telephonenumber”,“golfhandicap”以及“mail”,其中条目有属性“mail”并且“sn”属性的值是“Geisel”。这个例子产生如下结果。(条目中没有“golfhandicap”属性,所以没有返回)。
# java Search >>>cn=Ted Geisel attribute: sn value: Geisel attribute: mail value: Ted.Geisel@JNDITutorial.com attribute: telephonenumber value: +1 408 555 5252 |
过滤器
除了使用指定属性集合进行搜索外,您可以用搜索过滤器的形式进行搜索。搜索过滤器是一种搜索用的逻辑表达式。DirContext.search()可接受的过滤器语法在RFC 2254中定义。
以下搜索过滤器指定了满足查询条件的条目必须有值为“Geisel”的“sn”属性,以及值为任意的“mail”属性:
(&(sn=Geisel)(mail=*)) |
以下代码创建了过滤器以及默认的SearchControls,使用他们执行查询。这个查询的结果和基本查询中的一致。
// Create the default search controls SearchControls ctls = new SearchControls();
// Specify the search filter to match // Ask for objects that have the attribute “sn” == “Geisel” // and the “mail” attribute String filter = “(&(sn=Geisel)(mail=*))”;
// Search for objects using the filter NamingEnumeration answer = ctx.search(“ou=People”, filter, ctls); |
搜索结果如下:
# java SearchWithFilterRetAll >>>cn=Ted Geisel attribute: sn value: Geisel attribute: objectclass value: top value: person value: organizationalPerson value: inetOrgPerson attribute: jpegphoto value: [B@1dacd75e attribute: mail value: Ted.Geisel@JNDITutorial.com attribute: facsimiletelephonenumber value: +1 408 555 2329 attribute: cn value: Ted Geisel attribute: telephonenumber value: +1 408 555 5252 |
搜索过滤器语法概述
搜索过滤器是前缀标记的搜索表达式(逻辑运算符在表达式前面)。下表列举了创建过滤器使用的符号。
符号 | 描述 |
& | 与(列表中所有项必须为true) |
| | 或(列表中至少一个必须为true) |
! | 非(求反的项不能为true) |
= | 相等(根据属性的匹配规则) |
~= | 近似等于(根据属性的匹配规则) |
>= | 大于(根据属性的匹配规则) |
<= | 小于(根据属性的匹配规则) |
=* | 存在(条目中必须有这个属性,但值不做限制) |
* | 通配符(表示这个位置可以有一个或多个字符),当指定属性值时用到 |
\ | 转义符(当遇到“*”,“(”,“)”时进行转义) |
过滤器中的每一个条目使用属性标识符和属性值或符号表示属性值组成。例如,项“sn=Geisel”表示“sn”属性的值必须为“Geisel”,同时项“mail=*”表示“mail”属性必须存在。
每项必须包含在括号之内,例如“(sn=Geisel)”。这些项使用逻辑运算符,例如&,创建逻辑表达式,例如“(& (sn=Geisel) (mail=*))”。
每一个逻辑表达式可以进一步组成其他项,例如“(| (& (sn=Geisel) (mail=*)) (sn=L*))”。这个例子请求的条目中或者含有值为“Geisel”的“sn”属性和“mail”属性或者“sn”属性以字母“L”开头。
关于语法的详细描述,请参考RFC 2254。
返回选中属性
上一个例子返回满足指定过滤器的条目中的所有属性。您可以通过设置搜索控制参数的方法选择返回属性。您创建想要包含在结果中的属性标识符集合,然后将他传递到SearchControls.setReturningAttributes()中。如下所示:
// Specify the ids of the attributes to return String[] attrIDs = {“sn”, “telephonenumber”, “golfhandicap”, “mail”}; SearchControls ctls = new SearchControls(); ctls.setReturningAttributes(attrIDs); |
这个例子和基本搜索一节中返回选择的属性部分的结果一致。运行后的结果如下。(这个条目没有“golfhandicap”属性,所以没有返回)。
# java SearchWithFilter >>>cn=Ted Geisel attribute: sn value: Geisel attribute: mail value: Ted.Geisel@JNDITutorial.com attribute: telephonenumber value: +1 408 555 5252 |
范围
默认的SearchControls指定搜索只在命名空间中进行(SearchControls.ONELEVEL_SCOPE)。这个默认选项在搜索过滤器一节中使用。
除了默认选项之外,您可以指定搜索在整个子树或只在命名对象中执行。
搜索子树
对于整个子树的搜索不但搜索命名对象而且搜索它的后代。要按照这种方式进行搜索,按照下面的方式向SearchControls.setSearchScope()中传递SearchControls.SUBTREE_SCOPE参数:
// Specify the ids of the attributes to return String[] attrIDs = {“sn”, “telephonenumber”, “golfhandicap”, “mail”}; SearchControls ctls = new SearchControls(); ctls.setReturningAttributes(attrIDs); ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
// Specify the search filter to match // Ask for objects that have the attribute “sn” == “Geisel” // and the “mail” attribute String filter = “(&(sn=Geisel)(mail=*))”;
// Search the subtree for objects by using the filter NamingEnumeration answer = ctx.search(“”, filter, ctls); |
这个例子搜索了ctx上下文的子树,得到满足搜索过滤器的条目。它在子树中找到了满足过滤器的“cn= Ted Geisel, ou=People”条目。
# java SearchSubtree >>>cn=Ted Geisel, ou=People attribute: sn value: Geisel attribute: mail value: Ted.Geisel@JNDITutorial.com attribute: telephonenumber value: +1 408 555 5252 |
搜索命名对象
您也可以搜索命名对象。这样做是很有用的,例如,测试命名对象是否满足搜索过滤器。为了搜索命名对象,将SearchControls.OBJECT_SCOPE传递到setSearchScope()中。
// Specify the ids of the attributes to return String[] attrIDs = {“sn”, “telephonenumber”, “golfhandicap”, “mail”}; SearchControls ctls = new SearchControls(); ctls.setReturningAttributes(attrIDs); ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
// Specify the search filter to match // Ask for objects that have the attribute “sn” == “Geisel” // and the “mail” attribute String filter = “(&(sn=Geisel)(mail=*))”;
// Search the subtree for objects by using the filter NamingEnumeration answer = ctx.search(“cn=Ted Geisel, ou=People”, filter, ctls); |
这个例子测试是否对象“cn=Ted Geisel, ou=People”满足给定的过滤器。
# java SearchObject >>> attribute: sn value: Geisel attribute: mail value: Ted.Geisel@JNDITutorial.com attribute: telephonenumber value: +1 408 555 5252 |
例子找到了一个结果并进行打印。注意结果中的名称字段为空。因为对象的名称是进行查询的上下文(cn=Ted Geisel, ou=People)。
结果数量
有时,查询可能返回了太多的结果同时您想限制结果集的大小。这时您可以使用限制数量的搜索控制。默认情况下,搜索没有数量限制――它将会返回找到的所有结果。要设置搜索的数量限制,将数字传递到SearchControls.setCountLimit()中。
以下例子设置数量限制为1。
// Set the search controls to limit the count to 1 SearchControls ctls = new SearchControls(); ctls.setCountLimit(1); |
如果程序尝试得到更多的结果,那么会抛出SizeLimitExceededException。如果程序设置数量限制,那么或者将这个异常和NamingExceptions异常区别对待或者按照数量限制的大小,不请求超过数量的结果。
满足搜索数量限制是控制程序消耗资源的一种方法(例如内存和网络带宽)。其他控制资源的方法是尽量使用小的搜索过滤器,在合适的上下文中开始搜索,使用合适的范围。
时间限制
搜索时间限制是搜索操作等待应答的时间上限。当您不想为应答等待太长时间时这很有用。如果搜索操作超过时间限制还没完成,那么将会抛出TimeLimitExceededException异常。
为了设置搜索的时间限制,将毫秒数传递到SearchControls.setTimeLimit()即可。以下例子将时间限制设置为1秒。
// Set the search controls to limit the time to 1 second (1000 ms) SearchControls ctls = new SearchControls(); ctls.setTimeLimit(1000); |
要让这个例子运行超时,需要重新配置,或者使用一个慢的服务器或这使用有很多条目的服务器。您还可以使用其他方式让搜索超过1秒。
时间限制为0表示不进行时间限制,这样请求将会进行无限等待。
常见问题解答
当您使用JNDI类运行成功编译的程序时可能遇到的主要问题如下:
l 没有初始化上下文
l 连接被拒绝
l 连接失败
l 程序挂起
l 名字没有找到
l 不能连接任意主机
l 不能配置访问系统属性
l 不能使用CRAM-MD5认证
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服务器。通常,可以使用管理控制台或工具管理服务器。您可以使用工具确认服务器的状态。
3. LDAP服务器向其他工具应答(例如管理控制台)但不应答您程序的请求。
原因:服务器没有应答LDAP v3的连接全逆光球。一些服务器(尤其是公共服务器)不能正确的应答LDAP v3,使用忽略的方式代替拒绝。同时,一些LDAP v3服务器有错误处理机制,Sun的LDAP服务提供者自动发送并且通常返回特定服务器错误码。
解决方案:尝试设置环境参数“java.naming.ldap.version”为“2”。LDAP服务提供者默认尝试使用LDAP v3连接LDAP服务器,然后使用LDAP v2。如果服务器静默忽略v3的请求,那么提供者假设请求生效了。使用这种服务器,您必须显式的设置协议版本,确保服务器有正确的行为。
如果服务器是v3服务器,那么尝试在创建初始化上下文之前设置这些环境参数:
env.put(Context.REFERRAL, “throw”); |
这样关闭了LDAP提供者自动发送的控制(更多信息请参考JNDI教程)。
4. 程序挂起。
原因:当您尝试执行的查实会生成太多结果或需要服务器查询很多条目才能生成结果时,服务器(尤其是公共的)不应答(不是一个失败应答)。这种服务器基于预请求计算花费的方式尝试减少资源消耗。
或者,您尝试使用安全套接字层(SSL)但服务器/端口不支持,反之(您尝试使用普通套接字与SSL端口对话)。
最终,服务器或者因为负载原因非常慢的应答,或完全不应答某些请求。
解决方案:如果程序因为服务器为了减少资源消耗而挂起,那么重试请求会得到单一应答或只有很少的应答。这样可以帮助您判断服务器是否还在活动。这样,您可以加宽原有查询,重新发送。
如果您的程序因为SSL问题挂起,那么您需要找到SSL端口然后正确设置Context.SECURITY_PROTOCOL环境参数。如果端口是SSL端口,那么这个参数应该设置成“ssl”。如果不是SSL端口,这个参数不应该设置。
如果程序不是因为上述原因挂起,使用com.sun.jndi.ldap.read.timeout表示读取超时。这个参数的值是一个字符串,表示LDAP请求读取超时的毫秒数。如果LDAP提供者不能在周期内得到应答,那么放弃读取尝试。数字应该大于0。等于或小于0表示不指定读取超时时间,等于无限等待得到应答。
如果没有指定这个参数,默认情况下会一直等待,直到得到应答。
例如:
env.put(“com.sun.jndi.ldap.read.timeout”, “5000”); |
表示如果LDAP服务器不能在5秒中内应答,将放弃读取请求。
5. 您得到NameNotFoundException异常。
原因:当您为LDAP初始化了初始上下文,提供了根辨别名。例如,如果您为初始上下文设置Context.PROVIDER_URL为“ldap://ldapserver:389/o=JNDITutorial”,然后提供名称“cn=Joe,c=us”,那么您向LDAP服务传递的全名为“cn=Joe,c=us,o=JNDITutorial”。如果这确实是您想要的名称,那么您应该检验服务器确定包含这个条目。
同时,如果您在认证时提供错误的辨别名,Sun Java目录服务器返回错误。例如,如果您设置Context.SECURITY_PRINCIPAL 环境参数为“cn=Admin, o=Tutorial”,并且“cn=Admin, o=Tutorial”不是LDAP服务器的条目,LDAP提供者将要抛出NameNotFoundException。Sun Java目录服务器返回的实际上是一些认证异常,不是“name not found”。
解决方案:确认您提供的名字是服务器上存在的。您可以通过列举父上下文的所有条目或使用其他工具例如服务器的管理员控制台来确认。
以下是在部署applet时可能遇到的问题。
6. 当您的applet尝试连接目录服务器时得到AppletSecurityException异常,服务器正运行在和下载applet不同的机器上。
原因:applet没有签名,所以只能连接到加载它的机器。或者,如果appet已经签名,浏览器没有授予applet连接目录服务器的权限。
解决方案:如果您想允许applet连接任意机器上的目录服务器,那么需要签名整个applet以及applet使用的JNDI jar。关于jar的签名,请参考签名和验证jar文件。
7. 当您的applet尝试使用系统属性设置环境属性时出现AppletSecurityException异常。
原因:浏览器限制访问系统参数,并且当您尝试读取时抛出SecurityException。
解决方案:如果您需要为applet得到输入,尝试使用applet params代替。
8. 当applet运行在Firefox中尝试使用CRAM-MD5向LDAP进行认证时抛出AppletSecurityException。
原因:Firefox禁止访问java.security包。LDAP提供者使用java.security.MessageDigest提供的信息摘要功能来实现CRAM-MD5。
解决方案:使用Java插件。