命名异常
JNDI 包中的许多方法在需要指示无法执行请求的操作时会抛出NamingException
。通常,您会看到围绕可能引发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
的特定子类,您需要单独捕获子类。例如,以下代码特别处理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()
并指定要返回多少个答案的计数限制(n),则search()
将返回最多 n 个结果的枚举。如果结果数量超过 n,则当第 n+1 次调用NamingEnumeration.hasMore()
时,将抛出SizeLimitExceededException
。请参阅本课程的结果计数示例代码。
本教程中的示例
在本教程文本中嵌入的内联示例代码中,通常为了可读性而省略了try/catch
子句。通常,因为这里只显示代码片段,所以只包含直接用于说明概念的行。如果查看本教程附带的源文件,您将看到用于NamingException
的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()
的结果转换为目标类。
例如,以下代码查找对象"cn=Rosanna Lee,ou=People"
并将其转换为LdapContext
。
import javax.naming.ldap.LdapContext; ... LdapContext ctx = (LdapContext) ctx.lookup("cn=Rosanna Lee,ou=People");
完整的示例在文件Lookup.java
中。
Java SE 6 中有两个新的静态方法可用于查找名称:
这些方法提供了一种快捷的查找名称的方式,而无需实例化 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()
返回一个Binding
的枚举。Binding
是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
可以以自然、显式或意外的方式终止。
- 当
NamingEnumeration.hasMore()
返回false
时,枚举已完成并实际上被终止。 - 您可以通过调用
NamingEnumeration.close()
在枚举完成之前显式终止枚举。这样做会向底层实现提供一个提示,释放与枚举相关的任何资源。 - 如果
hasMore()
或next()
抛出NamingException
,则枚举实际上被终止。
无论枚举如何被终止,一旦终止,就不能再使用。在终止的枚举上调用方法会产生未定义的结果。
为什么有两种不同的列出方法?
list()
适用于浏览器样式的应用程序,只需显示上下文中对象的名称。例如,浏览器可能会列出上下文中的名称,并等待用户选择其中一个或几个名称以执行进一步操作。这类应用程序通常不需要访问上下文中的所有对象。
listBindings()
适用于需要对上下文中的对象进行批量操作的应用程序。例如,备份应用程序可能需要对文件目录中的所有对象执行“文件统计”操作。或者打印机管理程序可能希望重新启动建筑物中的所有打印机。为了执行这些操作,这些应用程序需要获取上下文中绑定的所有对象。因此,将对象作为枚举的一部分返回更为方便。
应用程序可以使用list()
或可能更昂贵的listBindings()
,具体取决于它所需的信息类型。
添加、替换或移除绑定
Context
接口包含用于在上下文中添加、替换和移除绑定的方法。
添加绑定
Context.bind()
用于向上下文添加绑定。它接受对象的名称和要绑定的对象作为参数。
在继续之前: 本课程中的示例需要您对模式进行添加。您必须在 LDAP 服务器中关闭模式检查,或者将伴随本教程的模式
添加到服务器中。这两项任务通常由目录服务器的管理员执行。请参阅 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()
相同的参数,但语义是如果名称已经绑定,则将解绑并绑定新给定的对象。
// 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()
或rebind()
例子创建的绑定。
重命名
您可以使用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 服务器中关闭模式检查,或者将伴随本教程的模式
添加到服务器中。这两项任务通常由目录服务器的管理员执行。请参阅 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);
This example
创建了一个名为"ou=NewOu"
的新上下文,在上下文ctx
中有一个属性"objectclass"
,其值为"top"
和"organizationalUnit"
。
# java Create ou=Groups: javax.naming.directory.DirContext ou=People: javax.naming.directory.DirContext ou=NewOu: javax.naming.directory.DirContext
This example
创建了一个名为"NewOu"
的新上下文,它是ctx
的子上下文。
销毁上下文
要销毁一个上下文,您需要向destroySubcontext()
提供要销毁的上下文的名称。
// Destroy the context ctx.destroySubcontext("NewOu");
This example
在上下文ctx
中销毁了上下文"NewOu"
。
属性名称
一个属性由一个属性标识符和一组属性值组成。属性标识符,也称为属性名称,是一个字符串,用于标识属性。属性值是属性的内容,其类型不限于字符串。当您想要为检索、搜索或修改指定特定属性时,您使用属性名称。名称也会被返回操作返回的属性(例如在目录中执行读取或搜索时)。
在使用属性名称时,您需要了解某些目录服务器功能,以免对结果感到惊讶。这些功能在下一小节中描述。
属性类型
在 LDAP 等目录中,属性的名称标识属性的类型,通常称为属性类型名称。例如,属性名称"cn"
也称为属性类型名称。属性的类型定义指定属性值应具有的语法,它是否可以具有多个值,以及在执行比较和排序操作时使用的相等性和排序规则。
属性子类化
一些目录实现支持属性子类化,其中服务器允许以其他属性类型定义属性类型。例如,"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"
。要检索此对象的属性,您需要类似于以下的code
:
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.example.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"
属性。此对象除了"golfhandicap"
属性外,其余属性都存在,因此答案中返回了三个属性。以下是示例的输出。
# java Getattrs attribute: sn value: Geisel attribute: mail value: Ted.Geisel@JNDITutorial.example.com attribute: telephonenumber value: +1 408 555 5252
修改属性
DirContext
接口包含了修改目录中对象的属性和属性值的方法。
Java 中文官方教程 2022 版(四十七)(2)https://developer.aliyun.com/article/1488495