您可使用JNDI執行如下操做:讀取操做和更新命名空間的操做。本節介紹這兩個操做:java
l 查詢對象數組
l 列出上下文內容瀏覽器
l 添加、覆蓋和移除綁定安全
l 重命名對象服務器
l 建立和銷燬子上下文網絡
在命名和目錄服務中執行操做以前,須要獲得初始化上下文――命名空間的開始點。由於命名和目錄服務的全部方法都相對於一些上下文執行。架構
爲了獲得初始化上下文,必須執行如下步驟:app
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()返回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()方法返回綁定的枚舉。綁定是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能夠經過三種方式終止:通常的,顯式的,非顯式的。
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插件。