在Apache目录服务器中存储Java对象,第2部分:(上)

在 ApacheDS 中存储、搜索和检索 Java 对象

简介:在第 2 部分中将介绍如何在 Apache 目录服务器 (ApacheDS) 中存储 Java™ 对象, Bilal Siddiqui 将提供 9 个示例应用程序,演示在 第 1 部分 中学习的概念。除 了介绍使用 ApacheDS 存储、搜索、检索和修改 Java 对象的所有步骤之外,Bilal 还将在总结全文时提 供一个可重用的 Java 类,该类可以使用 LDAP 模式组件在 ApacheDS 中将这些功能组合在一起。

在 第 1 部分 中,我介绍了在 ApacheDS 中存储 Java 对象的概念方面的基础,解释了 ApacheDS 的 核心架构,并讨论了它实现目录服务和可插入协议支持的方式。我还介绍了一些 LDAP 概念和术语,解释 了 ApacheDS 如何实现 LDAP 协议,并介绍了用来在 ApacheDS 中存储和操纵对象的各种组件。最后,讨 论了 Java 对象和 RMI 的基础,要想在 ApacheDS 中存储和检索 Java 对象,就必须理解它们。我还引 入了一个示例应用程序 —— 一个面向制造企业的数据管理系统,并用它演示文中讨论的一些概念。

在本系列的第 2 部分,我几乎完全依靠示例(总共有 9 个示例)。这些示例基于第 1 部分介绍的数 据管理系统,它们的作用是让您了解如何在 ApacheDS 中存储、搜索、检查和更新 Java 对象。

如果还没有下载和安装 ApacheDS,那么一定要在开始之前 下载和安装 ApacheDS 和 JXplorer。可以 下载 文章的完整源代码。

注意!

请注意,要跟上本文中的示例,必须理解基本的 LDAP 术语和概念,例如专有名称 (DN)、相对专有名 称 (RDN)、命名上下文、对象类和属性类型。如果还不熟悉这些术语,请在继续之前阅读 第 1 部分。

应用程序 1. 存储 Java 对象

我先从几个应用程序开始,演示如何在 ApacheDS 中存储 Java 对象。出于这个目的,需要使用 Java 命名和目录接口 (JNDI),它提供了操作目录中的对象和属性的接口和方法。请参阅 在 Apache 目录服务 器中存储 Java 对象,第 1 部分,获得 ApacheDS 如何使用 JNDI 接口公开目录服务的讨论。

JNDI 不是特定于 LDAP 的接口,因此可以拥有针对任何目录服务类型的 JNDI 实现。如果想实现自己 的目录服务并用 JNDI 公开它的功能,则需要为目录服务实现 JNDI 接口。注意,Java 2 标准版 (J2SE) 提供了 LDAP 的客户端 JNDI 实现,可以用它与 ApacheDS 对话。在我的讨论中,我将使用这个客户端实 现。

清单 1 是一个名为 SToreAlicePreferences 的简单应用程序。我将用这个应用程序介绍如何将用户 Alice 的选项作为 Java 对象存储到 ApacheDS 中。

清单 1. SToreAlicePreferences

public class SToreAlicePreferences {   public SToreAlicePreferences ()   {     try {       //------------------------------------------       //Step1: Setting up JNDI properties for ApacheDS       //------------------------------------------       InputStream inputStream = new FileInputStream ( "ApacheDS.properties");       Properties properties = new Properties();       properties.load(inputStream);       properties.setProperty("java.naming.security.credentials",  "secret");       //------------------------------------------       //Step2: Fetching a DirContext object       //------------------------------------------       DirContext ctx = new InitialDirContext(properties);       //------------------------------------------       //Step3: Instantiate a Java object       //------------------------------------------       MessagingPreferences preferences = new MessagingPreferences ();       //------------------------------------------       //Step4: STore the Java object in ApacheDS       //------------------------------------------       String bindContext = "cn=preferences,uid=alice,ou=users";       ctx.bind( bindContext, preferences);     } catch (Exception e) {       System.out.println("Operation failed: " + e);     }   }   public static void main(String[] args) {     SToreAlicePreferences sToreAlicePref = new SToreAlicePreferences();   }}

从清单 1 中的注释中可以看出,将 Java 对象(即 Alice 的选项)存储到 ApacheDS 中包括 4 个步 骤。后面几节将详细讨论每个步骤。

步骤 1. 设置 ApacheDS 的 JNDI 属性

清单 1 中的第一步是将 ApacheDS 的属性文件读入 Properties 对象。这意味着首先必须将 JNDI 属 性写入单独的属性文件,如清单 2 所示:

清单 2. ApacheDS.properties 文件

java.naming.facTory.initial=com.sun.jndi.ldap.LdapCtxFacToryjava.naming.provider.url=ldap://localhost:389/ou=systemjava.naming.security.authentication=simplejava.naming.security.principal=uid=admin,ou=system

之所以需要单独的属性文件,是因为使用客户端 JNDI 实现的应用程序可能独立于特定的 JNDI 实现 而工作。指定的 Java 应用程序(例如 SToreAlicePreferences)应当能够操作 ApacheDS 或其他任何目 录服务。目录服务甚至不需要使用 LDAP。

考虑一个简单的场景,属性文件的价值就会变得很清楚。假设您已经开发了一个 Java 应用程序,该 应用程序使用 LDAP 的客户端 JNDI 实现与 ApacheDS 进行对话。后来,您又购买了另一个不使用 LDAP 协议而使用其他协议的目录服务器。

在这种情况下,需要一个能够操作新目录服务器的新的客户端 JNDI 实现。Java 程序程序将在不需要 任何重新编码的情况下继续工作,只需要更新属性文件来反映新目录服务器的属性即可。

虽然在应用程序中对属性进行硬编码不会有什么编程问题,但是这么做会让应用程序依赖那些您已对 其进行了硬编码的特定实现。这有点违背使用 JNDI 的目的,它的目的是保持独立于特定的实现。

属性文件的细节

现在查看 清单 2 中所示的 ApacheDS.properties 文件。属性文件由许多名称-值对组成,每个名称 代表一个属性。

在实例化公开名为 Context 的 JNDI 接口的对象时,会使用这些属性。Context 实际上是 JNDI 中最 重要的接口。这个接口定义了操作命名上下文的方法。(在 第 1 部分 中的示例应用程序中,我介绍了 命名上下文的概念。)

例如,Context 接口中定义的一个重要方法是 bind(),它将 Java 对象绑定到命名上下文。将对象绑 定到命名上下文意味着要用特定名称将对象存储在目录的特定上下文中。稍后我将演示如何使用 bind() 方法。首先,我们来查看属性文件中的名称-值对。

名称-值对

清单 2 中的第一个名称-值对是 java.naming.facTory.initial=com.sun.jndi.ldap.LdapCtxFacTory ,它设置了 JNDI 属性 java.naming.facTory.initial。java.naming.facTory.initial 属性指定了用作 JNDI 客户端实现的一部分的对象工厂的名称。这个对象工厂创建了 Context 对象,可以将这个对象与 ApacheDS 中的命名上下文一起使用。

因为使用基于 LDAP 的 JNDI 实现,所以可以指定 com.sun.jndi.ldap.LdapCtxFacTory 作为这个属 性的值。正如您可以猜到的,com.sun.jndi.ldap.LdapCtxFacTory 类构建的 Context 对象能够根据 LDAP 协议与 ApacheDS 进行通信。

清单 2 中的第二个名称-值对是 java.naming.provider.url=ldap://localhost:389/ou=system。 java.naming.provider.url 属性指定了要处理的完整目录上下文的 URL。

完整目录上下文包含两个组件:一个组件是 URL,ApacheDS 正在其指示的位置上进行侦听,另一个组 件是命名上下文,ApacheDS 在该上下文中工作。字符串 ldap://localhost:389/ 指定 ApacheDS 在其指 示的位置上进行侦听的 URL。字符串的其余部分(ou=system)指定要处理的命名上下文。

第三个名称-值对是 java.naming.security.authentication=simple。这个属性指定 ApacheDS 进行 用户身份验证时使用的安全强度。这个属性可以是以下三个值之一:none、simple 或 strong。

如果选择 “none”,则 ApacheDS 不使用身份验证,任何人都可以不指定口令就进行登录。

如果选择 “simple”,则 ApacheDS 采用基于口令的简单身份验证,这意味着口令以明文方式在网络 上传递。

如果选择 “strong”,那么用户口令以散列值形式(而不是明文形式的实际口令)传递给 ApacheDS 进行身份验证。

ApacheDS 的当前版本不支持 “strong” 级的身份验证。

清单 2 中的第四个名称-值对是 java.naming.security.principal=uid=admin,ou=system。这个属性 指定了要登录到 ApacheDS 的用户的 DN。(我使用 ApacheDS 管理员的 DN(uid=admin,ou=system)作 为这个属性的值。)

我们已经在 清单 2 中查看了 ApacheDS.properties 文件中的 4 个名称-值对。现在再来看一下 清 单 1 中的步骤 1 ,在这一步骤中,将把属性文件读入 Properties 对象中。不久,在处理 JNDI 时将会 使用这个 Properties 对象。

设置用户口令

还需要在 Properties 对象中包含用户的口令。但是真正的应用程序通常不会在配置文件中存储用户 口令;它会通过 GUI 接收用户的口令。在 清单 1 中,我将口令设为 java.naming.security.credentials 属性的值。这个属性实际上接收一个证明用户身份的凭证。可能有 多种凭证(例如,口令或 Kerberos 票据);对于本文,我使用基于口令的身份验证。

所有属性均已设置,可以使用 Properties 对象了。

步骤 2. 获得 DirContext 对象

接下来,实例化叫做 InitialDirContext 的类。这个类是 JNDI 的一部分,该类用于公开叫做 DirContext 的接口。InitialDirContext 的构造函数接受上面讨论的 Properties 对象。

InitialDirContext 对象能够执行您想在 ApacheDS 上执行的所有目录操作,包括存储新对象、搜索 已经存储的对象、向现有对象添加属性,等等。

DirContext 接口

DirContext 接口扩展了 Context 接口。Context 接口表示命名上下文,DirContext 接口则提供与添 加、删除和管理命名上下文有关的属性的功能。

简言之,Context 接口提供命名功能,DirContext 接口扩展命名功能,添加对属性的支持。命名和属 性功能共同构成了目录服务。

您可能会说 InitialDirContext 对象是由工厂对象实例化的 DirContext 对象的包装器。在这个示例 中,InitialDirContext 的构造函数使用了 清单 2 的第一个属性指定的上下文工厂对象(即 com.sun.jndi.ldap.LdapCtxFacTory)。工厂对象实例化了一个公开 DirContext 对象的对象,而 InitialDirContext 对象则使用这个 DirContext 对象执行客户机应用程序要求的目录操作。

使用 ApacheDS 的优势

ApacheDS 目录服务的主要优势在于:让客户机应用程序独立于任何特定实现。客户机应用程序在配置 文件中指定工厂方法,InitialDirContext 对象用工厂方法实例化了 DirContext 对象,该对象包含处理 远程目录服务通信所需的所有逻辑。

例如,清单 1 使用了来自 Sun Microsystem 的 com.sun.jndi.ldap.LdapCtxFacTory 工厂对象。这 个工厂对象创建的 DirContext 对象能够制定 ApacheDS 可以理解的 LDAP 请求。

如果以后想使用一些非 LDAP 服务运行 SToreAlicePreferences 应用程序(来自 清单 1),那么只 需根据非 LDAP 服务的业务逻辑,用新的工厂对象交换 清单 2 中的工厂对象的名称即可。然后 SToreAlicePreferences 就可以开始使用非 LDAP 服务了。

步骤 3. 实例化 Java 对象

接下来将实例化叫做 MessagingPreferences 的类,如清单 3 所示,这个类代表 Alice 的消息传递 选项。(请回忆一下第 1 部分中对 消息传递选项 的讨论。)

清单 3. MessagingPreferences 类

public class MessagingPreferences extends   Preferences implements java.io.Serializable {   static final long serialVersionUID = -1240113639782150930L;   //Methods of the MessagingPreferences class}

现在您还可以调用 MessagingPreferences 类的方法来设置 Alice 的选项。

在清单 3 中,MessagingPreferences 类实现了 Serializable 接口(第 1 部分的 “序列化 Java 对象” 一节中介绍过),在继续进行后面的内容之前,我将简要讨论一下叫做 serialVersionUID 的内 容。

获得 serialVersionUID

建议让所有可序列化的类都包含类型为 long、名为 serialVersionUID 的私有静态数据成员。不需要 在可序列化类中到处使用这个数据成员。Java 运行库在序列化和反序列化期间使用这个数据成员。

Java 对象序列化规范指定了一个复杂的算法来计算 serialVersionUID 的值。该算法使用了可序列化 的名称、类实现的所有接口的名称、可序列化类的所有数据成员,等等。不需要考虑这个复杂算法的细节 ;Java 平台提供了叫做 serialver 的工具,该工具也可以计算这个值。

要为 MessagingPreferences 对象建立 serialVersionUID,可以从命令行按如下所示方式使用 serialver 工具:

X:/jdk1.5/bin/serialver MessagingPreferences

正如您可以看到的,我在 清单 3 中已经为 MessagingPreferences 类实现了同样的操作。

步骤 4. 在 ApacheDS 中存储 Java 对象

现在已经设置了 DirContext 对象和 MessagingPreferences 对象,可以继续后面的步骤了。剩下的 是用 DirContext 对象在 ApacheDS 中存储 MessagingPreferences。

在 LDAP 服务器中存储数据条目叫做绑定 操作。Context 接口有一个方法叫做 bind(),可以用它在 ApacheDS 中存储 Java 对象。在 清单 1 的步骤 4 中可以看到 bind() 的用法。

设置 Context.bind() 的参数

Context.bind() 方法采用了两个参数。第一个参数 (cn=preferences,uid=alice,ou=users,ou=system)指定存储 Java 对象的命名上下文。这个命名上下 文可以分成两部分:cn=preferences 和 uid=alice,ou=users,ou=system,中间用逗号分隔。

因为新条目表示 Alice 的视图选项,所以可以用 cn=preferences 作为它的 RDN。注意,字符串 uid=alice,ou=users,ou=system 与 Alice 的数据条目的 DN 相同,第一次看到它是在第 1 部分的 “创 建 RDN” 一节中。

基于这些内容,新条目的 DN 是 cn=preferences,uid=alice,ou=users,ou=system,它是传递给 bind() 方法的第一个参数的值。

Context.bind() 方法调用的第二个参数是步骤 3 中的 MessagingPreferences 对象。bind() 方法调 用不返回任何结果。

运行第一个应用程序!

我把前面介绍的四个步骤组合在 清单 1 所示的一个应用程序 SToreAlicePreferences 中。在本文的 源代码 中也可以找到一些示例应用程序。

在运行 SToreAlicePreferences 应用程序之前,必须在 ApacheDS 中存储一个 DN 等于 uid=alice,ou=users,ou=system 的条目。在第 1 部分的 “创建 RDN” 小节中,我们已经创建了名为 Alice 的用户。

在运行 SToreAlicePreferences 应用程序之后,可以通过在 LDAP 浏览器中(在这个示例中是 JXplorer)展开 Alice 条目,确定已经将 Alice 的消息传递选项存储为 Java 对象。您应当看到 Alice 展开的视图,如图 1 所示:

图 1. Alice 的消息传递选项已经存储!

应用程序说明

图 1 所示的 MessagingPreferences 对象包含三个属性。这三个属性是 javaClassName、 javaClassNames 和 javaSerializedData,在第 1 部分的 “在 ApacheDS 中存储 Java 对象” 小节中 已经讨论过它们。

在 SToreAlicePreferences 应用程序中(在步骤 4)的 bind() 方法调用上,我没有包含这些属性, 所以您可能想知道它们是怎么到达 ApacheDS 的。答案是:bind() 方法自己编写了这些属性!带两个参 数的 Context.bind() 方法不接受任何属性。但是,正如第1 部分的 “在 ApacheDS 中存储 Java 对象 ” 一节中解释的那样,LDAP 需要 javaClassName、javaClassNames 和 javaSerializedData 属性。所 以 Context.bind() 方法自己编写了这些属性。

下一节将介绍带有三个参数的 bind() 方法,该方法采用了一组参数,并将它们与 Java 对象存储在 一起。

图 1 所示的 MessagingPreferences 对象使用了 javaContainer 对象类。第 1 部分的 “在 ApacheDS 中存储 Java 对象” 一节中已讨论了这个类。如果您愿意,不使用 javaContainer 类也能把 Java 对象写入 ApacheDS,就像下面的示例应用程序演示的那样。

应用程序 2. 存储带有属性的 Java 对象

在这个示例中,将学习如何用前面提到过的带三个参数的 bind() 方法将属性添加到 Java 对象中。 参见清单 4 中叫做 SToreBobPreferences 的示例应用程序。这个应用程序为名为 Bob 的用户创建了一 个条目,并且还在该条目中存储 Bob 的选项(即属性),它用一个操作就完成了这两个步骤。

清单 4. SToreBobPreferences

public class SToreBobPreferences{   public SToreBobPreferences ()   {     try {       //------------------------------------------       //Step1: Setting up JNDI properties for ApacheDS       //------------------------------------------       InputStream inputStream = new FileInputStream ( "apacheds.properties");       Properties properties = new Properties();       properties.load(inputStream);       properties.setProperty("java.naming.security.credentials",  "secret");       //----------------------------------------------       //Step2: Fetching a DirContext object       //----------------------------------------------       DirContext ctx = new InitialDirContext(properties);       //----------------------------------------------       //Step3A: Instantiate a Java Object       //----------------------------------------------       MessagingPreferences preferences = new MessagingPreferences ();       //----------------------------------------------       //Step3B: Instantiate BasicAttribute object       //----------------------------------------------       Attribute bjclass = new BasicAttribute("objectClass");       //----------------------------------------------       //Step3C: Supply value of attribute       //----------------------------------------------       objclass.add("person");       //----------------------------------------------       //Step3D: Put the attribute in attribute collection       //----------------------------------------------       Attributes attrs = new BasicAttributes(true);       attrs.put(objclass);       //----------------------------------------------       //Step4: STore the Java object in ApacheDS       //----------------------------------------------       String bindContext = "uid=Bob,ou=users";       ctx.bind( bindContext, preferences, attrs);     } catch (Exception e) {       System.out.println("Operation failed: " + e);     }   }   public static void main(String[] args) {     SToreBobPreferences sToreBobPref = new SToreBobPreferences();   }}

清单 4 中的大部分内容包含的步骤与 清单 1 相同,区别在于步骤 3 中有一些额外的代码,我将在 下面对此进行解释。(注意,清单 4 中的步骤 3A 与 清单 1 中的步骤 3 相同,所以我将从步骤 3B 开 始。)

步骤 3B. 实例化 BasicAttribute

在 SToreBobPreferences 应用程序与 SToreAlicePreferences 有所不同的第一个步骤中,实例化一 个名为 BasicAttribute 的 JNDI 类,该类公开了一个叫做 Attribute 的 JNDI 接口。Attribute 接口 可以表示 LDAP 数据条目的单一属性。BasicAttribute 类提供了 Attribute 接口的基本实现(功能有限 )。鼓励大家让自己的应用程序和实现拥有自己的 Attribute 接口实现(通过扩展 BasicAttribute 类 );但在这里,BasicAttribute 类提供了足够本文演示目的的功能。

BasicAttribute 构造函数采用属性名称作为参数。请注意 清单 4 中的步骤 3B,我构建的第一个 BasicAttribute 对象使用 objectClass 作为参数。这意味着 BasicAttribute 对象表示名为 objectClass 的属性。

对于想添加到 Bob 的数据条目中的每个属性,都要实例化一个 BasicAttribute 类。

步骤 3C. 为每个属性提供值

当您为想要包含在 Bob 数据条目中的每个属性提供一个 BasicAttribute 对象时,还要为每个属性提 供值。要提供值,则需要调用 Attribute 接口的 add() 方法。add() 方法只采用了一个参数,该参数是 想要提供的值的字符串形式。

add() 方法接受的实际是 Java Object 类的实例。因为所有 Java 对象都扩展自 Object 类,所以可 以将字符串值传递给 add() 方法。如果某些属性是多值的,那么可以多次调用属性的 add() 方法来提供 必要的值。

步骤 3D. 创建属性集合

您已经有了所有属性,现在需要将它们放入 Attribute 对象集合中。JNDI 提供了一个叫做 Attributes 的接口,并在叫做 BasicAttributes 的类中提供了该接口的基本实现。您可以实例化一个 BasicAttributes 对象,并多次调用对象的 put() 方法,将所有 Attribute 对象(一次一个地)放入集 合中。

如 清单 4 的步骤 4 所示,接下来将调用带有三个参数的 bind() 方法。带有三个参数的 bind() 方 法与 清单 1 中使用的带有两个参数的该方法类似,第三个参数是刚刚创建的属性集合。

应用程序 3. 存储编组的 Java 对象

在存储 Java 对象的最后这个练习中,我将介绍如何存储编组的 Java 对象,第 1 部分的 “表示编 组 Java 对象” 小节中已经简要讨论过它。

您打算以编组形式存储的 Java 对象应该是可序列化的(就像在 清单 1 的步骤 3 中创建的可序列化 的 MessagingPreferences 对象)。为了进行演示,我采用了同一个 MessagingPreferences 对象来演示 如何编组对象并将它存储在 ApacheDS 中。

首先请参见清单 5 中的 SToreAlicePreferencesInMarshalledForm. 应用程序,它演示了在 ApacheDS 中存储已编组 Java 对象的所有步骤:

清单 5. SToreAlicePreferencesInMarshalledForm

public class SToreAlicePreferencesInMarshalledForm. {   public SToreAlicePreferencesInMarshalledForm. ()   {     try {       //------------------------------------------       //Step1: Setting up JNDI properties for ApacheDS       //------------------------------------------       InputStream inputStream = new FileInputStream ( "ApacheDS.properties");       Properties properties = new Properties();       properties.load(inputStream);       properties.setProperty("java.naming.security.credentials",  "secret");       //------------------------------------------       //Step2: Fetching a DirContext object       //------------------------------------------       DirContext ctx = new InitialDirContext(properties);       //---------------------------------------------       //Step3: Instantiate a Java Object       //---------------------------------------------       MessagingPreferences preferences = new MessagingPreferences();       MarshalledObject mObj= new MarshalledObject(preferences );       //--------------------------------------------       //Step4: SToring Java object in ApacheDS       //--------------------------------------------       String bindContext = "cn=marshalledPreferences,uid=alice,ou=users";       ctx.bind( bindContext, mObj);     } catch (Exception e) {       System.out.println("Operation failed: " + e);     }   }   public static void main(String[] args) {     SToreAlicePreferencesInMarshalledForm. sToreAlicePref =       new SToreAlicePreferencesInMarshalledForm();   }}

J2SE 将内部处理编组过程,所以清单 5 中的应用程序非常类似 清单 1 所示的 SToreAlicePreferences 应用程序。如果比较两个应用程序,就会看到 清单 5 的步骤 3 中 只有一行额 外代码。在实例化 MessagingPreferences 对象后,还要实例化一个 java.rmi.MarshalledObject,将 preferences 对象传递给 java.rmi.MarshalledObject 构造函数。然后 java.rmi.MarshalledObject 类 将处理编组过程,并拥有 MessagingPreferences 对象的编组版本。

在步骤 4 中,只存储(或绑定)了编组对象而不是原来的 MessagingPreferences 对象。

这一节的内容结束了在 ApacheDS 中存储 Java 对象的讨论。现在来看两个示例,它们将演示在 ApacheDS 中进行的数据搜索。

本文配套源码

即使爬到最高的山上,一次也只能脚踏实地地迈一步。

在Apache目录服务器中存储Java对象,第2部分:(上)

相关文章:

你感兴趣的文章:

标签云: