1. 程式人生 > 其它 >Java之JNDI詳解

Java之JNDI詳解

JNDI的基本應用
JNDI是Java Naming and Directory Interface(JAVA命名和目錄介面)的英文簡寫,它是為JAVA應用程式提供命名和目錄訪問服務的API(Application Programing Interface,應用程式程式設計介面)。

1.命名的概念與應用
JNDI中的命名(Naming),就是將Java物件以某個名稱的形式繫結(binding)到一個容器環境(Context)中,以後呼叫容器環境(Context)的查詢(lookup)方法又可以查找出某個名稱所繫結的Java物件。讀者也許會感到奇怪:自己建立一個Java物件,將其繫結到JNDI容器環境中後又查詢出來,這有什麼意思?在真實的專案應用中,通常是由系統程式或框加程式先將資源物件繫結到JNDI環境中,以後在該系統或框架中執行的模組程式就可以從JNDI環境中查詢這些資源物件了。例如,Tomcat伺服器在啟動時可以建立一個連線到某種資料庫系統的資料來源(DataSource)物件,並將該資料來源(DataSource)物件繫結到JNDI環境中,以後在這個Tomcat伺服器中執行的Servlet和JSP程式就可以從JNDI環境中查詢出這個資料來源(DataSource)物件進行使用,而不用關心資料來源(DataSource)物件是如何創建出來的,這種方式極大地增強了系統的可維護性,當資料庫系統的連線引數發生變更時,這只是Tomcat系統管理員一個人要關心的事情,而與所有的應用程式開發人員無關。
容器環境(Context)本身也是一個Java物件,它也可以通過一個名稱繫結到另一個容器環境(Context)中。將一個Context物件繫結到另外一個Context物件中,這就形成了一種父子級聯關係,多個Context物件最終可以級聯成一種樹狀結構,樹中的每個Context物件中都可以繫結若干個Java物件,如圖6.10所示。


圖6.10
圖6.10中的每個方框分別代表一個Context物件,它們繫結的名稱分別為a、b、c、d、e,b和c是a的子Context,d是b的子Context,e又是d的子Context。圖9.x中的各個方框內的每個小橢圓分別代表一個Java物件,它們也都有一個繫結的名稱,這些繫結名稱分別為dog、pig、sheet等,在同一個Context不能繫結兩個相同名稱的Java物件,在不同的Context中可以出現同名的繫結物件。可見,Context樹的級聯結構與檔案系統中的目錄結構非常類似,Context與其中繫結的Java物件的關係也非常類似於檔案系統中的目錄與檔案的關係。從圖6.10中可以看到,要想得到Context樹中的一個Java物件,首先要得到其所在的Context物件,只要得到了一個Context物件,就可以呼叫它的查詢(lookup)方法來獲得其中繫結的Java物件。另外,呼叫某個Context物件的lookup方法也可以獲得Context樹中的任意一個Context物件,這隻需要在lookup方法中指定相應的Context路徑即可。在JNDI中不存在著“根”Context的概念,也就是說,執行JNDI操作不是從一個“根”Context物件開始,而是可以從Context樹中的任意一個Context開始。無論如何,程式必須獲得一個作為操作入口的Context物件後才能執行各種JNDI命名操作,為此,JNDI API中提供了一個InitialContext類來建立用作JNDI命名操作的入口Context物件。Context是一個介面,Context物件實際上是Context的某個實現類的例項物件,選擇這個具體的Context實現類並建立其例項物件的過程是由一個Context工廠類來完成的,這個工廠類的類名可以通過JNDI的環境屬性java.naming.factory.initial指定,也可以根據Context的操作方法的url引數的Schema來選擇。

2.目錄的概念與應用
JNDI中的目錄(Directory)與檔案系統中的目錄概念有很大的不同,JNDI中的目錄(Directory)是指將一個物件的所有屬性資訊儲存到一個容器環境中。JNDI的目錄(Directory)原理與JNDI的命名(Naming)原理非常相似,主要的區別在於目錄容器環境中儲存的是物件的屬性資訊,而不是物件本身,所以,目錄提供的是對屬性的各種操作。事實上,JNDI的目錄(Directory)與命名(Naming)往往是結合在一起使用的,JNDI API中提供的代表目錄容器環境的類為DirContext,DirContext是Context的子類,顯然它除了能完成目錄相關的操作外,也能完成所有的命名(Naming)操作。DirContext是對Context的擴充套件,它在Context的基礎上增加了對目錄屬性的操作功能,可以在其中繫結物件的屬性資訊和查詢物件的屬性資訊。JNDI中的目錄(Directory)的結構示意圖如圖6.11所示。


圖6.11
圖6.11中的每個最外層的方框分別代表一個DirContext物件,它們繫結的名稱分別為a、b,b是a的子DirContext。圖6.11中的各個最外層的方框內的每個小橢圓分別代表一個Java物件,各個裡層的方框分別代表一個物件的屬性。從名稱為a的DirContext中的內容可以看到,一個DirContext容器環境中即可以繫結物件自身,也可以繫結物件的屬性資訊,繫結的物件和繫結的屬性是完全獨立的兩個事物,即使它們的繫結名稱相同,它們的操作也是完全獨立的。另外,一個屬性可以有多個屬性值,例如,dog物件的category屬性就設定了兩個屬性值:meat和pet。從名稱為b的DirContext中的內容可以看到,一個DirContext容器環境中也可以只繫結物件的屬性資訊,而不繫結任何物件自身。與Context的操作原理類似,JNDI API中提供了一個InitialDirContext類來建立用作JNDI命名與目錄屬性操作的入口DirContext物件。

3. 用於DNS查詢的JNDI服務程式
JNDI API是面向應用程式開發人員的程式設計介面,它在執行時需要呼叫某個具體的JNDI服務程式,JNDI API與JNDI服務程式之間的關係,猶如JDBC與JDBC驅動程式之間的關係。從JDK 1.3開始,JDK中就集成了JNDI API,從JDK 1.4開始的版本又集成了用於DNS查詢的JNDI服務程式,所以,如果我們使用JDK 1.4及更高的JDK版本來開發DNS資訊查詢程式時,不需要下載和安裝JNDI API和用於DNS查詢的JNDI服務程式。SUN公司提供的用於查詢DNS資訊的JNDI服務程式,將某個域名的DNS資訊以屬性的形式繫結到代表該域名的DirContext物件上。開啟JDK幫助文件的首頁,在其中搜索“jndi”關鍵字,可以看到一條“jndi”的超連結,單擊這個超連結,就可以進入“Java Naming and Directory Interface”的幫助頁面,如圖6.12所示。


圖6.12
單擊圖6.12中的“The DNS Service Provider”超連結,進入DNS服務程式的幫助頁面。只要我們具備JDNI程式設計的一些基本知識,再加上該幫助文件頁提供的資訊,我們就知道如何呼叫這個DNS服務程式來獲得某個域的DNS資訊和MX記錄了。

下面編寫一個試驗性的JNDI程式,這個程式用於幫助我們熟悉和掌握JNDI API的使用,也幫助我們瞭解DNS的JNDI服務程式以怎樣的形式返回DNS資訊。

動手實踐:使用JNDI API獲取DNS資訊
按例程6-5編寫一個名為DNSQuery.java的程式,這個程式使用JNDI API來獲得某個域的DNS資訊,並從中提取出域的一臺SMTP伺服器的名稱,其中的很多程式碼都是為了幫助我們熟悉JNDI API的使用和了解DNS的JNDI服務程式返回的DNS資訊內容而加入的。執行這個程式時,需要指定一個或兩個引數,第一個引數是必須的,為要查詢的域名,第二個引數是可選的,為查詢時所使用的DNS伺服器的IP地址,如果沒有指定第二個引數,DNS的JNDI服務程式將使用底層作業系統上設定的DNS伺服器。

例程6-5 DNSQuery.java

import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class DNSQuery 
{
    public static void main(String[] args) throws NamingException 
    {
        /*第一個引數指定要查詢的域或主機名,第二個引數指定查詢的DNS伺服器,
        為了程式的簡單易讀性,省略了嚴格的引數錯誤檢查*/
        String domain = args[0];
        String dnsServer = args.length<2 ? "" : ("//" + args[1]);

        //通過環境屬性來指定Context的工廠類
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, 
                    "com.sun.jndi.dns.DnsContextFactory");
        env.put(Context.PROVIDER_URL, "dns:" + dnsServer);
        DirContext ctx = new InitialDirContext(env);
        //分別獲取包含所有屬性和只包含Mx屬性的Attributes物件
        Attributes attrsAll = ctx.getAttributes(domain);    
        Attributes attrsMx = ctx.getAttributes(domain, new String[]{"MX"}); 

        /*上面的整段程式程式碼也可以用下面這段程式程式碼來替代,下面這段程式
        程式碼通過查詢URL中的Schema資訊來自動選擇Context的工廠類*/
        /*
        DirContext ctx = new InitialDirContext();
        Attributes attrsAll = ctx.getAttributes("dns:" + dnsServer + "/" + domain);
        Attributes attrsMx = ctx.getAttributes(
            "dns:" + dnsServer + "/" + domain, new String[]{"MX"});         
        */

        System.out.println("打印出域" + domain + 
                                "的Attributes物件中的資訊:");
        System.out.println(attrsAll);
        System.out.println("--------------------------");
        System.out.println("列印只檢索域" + domain + 
                                "的MX記錄的Attributes物件:");     
        System.out.println(attrsMx);

        System.out.println("--------------------------");
        System.out.println("逐一打印出Attributes物件中的各個屬性:");         
        NamingEnumeration attributes = attrsAll.getAll();
        while(attributes.hasMore()) 
        {   
            System.out.println(attributes.next());
        }

        System.out.println("--------------------------");
        //直接呼叫get方法從attrsMx集合檢索MX屬性
        System.out.println("直接檢索Attributes物件中的MX屬性:");      
        Attribute attrMx = attrsAll.get("MX");
        System.out.println(attrMx); 

        System.out.println("--------------------------");           
        //獲取Mx屬性中的第一個值:
        System.out.println("獲取Mx屬性中的第一個值:");
        String recordMx = (String)attrMx.get();
        System.out.println(recordMx);
        //從Mx屬性的第一個值中提取郵件伺服器地址
        System.out.println("從MX屬性值中提取的郵件伺服器地址:");
        String smtpServer = recordMx.substring(
                            recordMx.indexOf(" ") + 1);
        System.out.println(smtpServer);
}

(2)在Windows命令列視窗中編譯DNSQuery.java程式後,接著在命令列視窗中執行如下命令:

ipconfig /all

如果ipconfig命令顯示的結果中包含有DNS Server的資訊,那麼我們接著就可以使用如下命令來啟動執行DNSQuery類:

java  DNSQuery  sina.com 

上面的命令的執行結果如圖6.13。


圖6.13
()假設在上一步用ipconfig命令檢視到的本地計算機上配置的DNS Server為202.106.46.151,那麼,我們接著執行如下命令:

java  DNSQuery  sina.com  202.106.46.151

這個命令執行完後,也能顯示出圖6.13中的資訊。我們接著故意將上面命令中的DNS伺服器引數指定為一個錯誤的IP地址進行執行,修改後的命令語句如下所示:

java  DNSQuery  sina.com  192.168.1.151

這個命令執行完後的結果如圖6.14所示:

圖6.14
如果計算機只能通過代理伺服器連線到Internet,那麼在該計算機上直接執行如下命令:

java  DNSQuery  sina.com 

這也將導致圖6.14中的錯誤。如果要想在通過代理伺服器上網的情況下,正確執行上面的程式,可以採用如下命令:

java -DsocksProxyHost=162.105.1.200 -DsocksProxyPort=808 DNSQuery  sina.com  202.106.46.151

由於上面的命令太長,在排版時分成了兩行來書寫,讀者在輸入上面這條命令時,不要手工換行。讀者應該根據自己的實際情況,修改其中的代理伺服器地址、代理埠號和DNS伺服器的地址。