在 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.LdapCtxFactory
java.naming.provider.url=ldap://localhost:389/ou=system
java.naming.security.authentication=simple
java.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 objclass = 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 中進行的數據搜索。
本文配套源碼