JNDI 在 J2EE 中的角色(個人覺得不錯)

掌握 J2EE 是件令人生畏的事,因爲它包含的技術和縮略語在不斷地增長。Java 命名和目錄接口(Java Naming and Directory Interface,JNDI)從一開始就一直是 Java 2 平臺企業版(JEE)的核心,但是 J2EE 開發新手經常用不好它。本文將消除 JNDI 在 J2EE 應用程序中所扮演角色的神祕性,並展示它如何幫助應用程序從部署細節中解脫出來。
雖然 J2EE 平臺提高了普通企業開發人員的生活水平,但是這種提高是以不得不學習許多規範和技術爲代價的,這些規範和技術則是 J2EE 爲了成爲無所不包的分佈式計算平臺而整合進來的。Dolly Developer 是衆多開發人員中的一員,她已經發現了一個特性,該特性有助於緩解隨企業級應用程序部署而帶來的負擔,這個特性就是 JNDI,即 Java 命名與目錄接口(Java Naming and Directory Interface)。讓我們來看看 Dolly 在沒有 JNDI 的時候是怎麼做的,以及她是如何正確地應用 JNDI 來改善其狀況的。
Dolly Developer 正在編寫使用 JDBC 數據源的 Web 應用程序。她知道自己正在使用 MySQL,所以她將一個對 MySQL JDBC 驅動程序類的引用進行了編碼,並通過使用適當的 JDBC URL 連接到其 Web 應用程序中的數據庫。她認識到數據庫連接池的重要性,所以她包含了一個連接池包,並把它配置成最多使用 64 個連接;她知道數據庫服務器已經被設置成最多允許 128 臺客戶機進行連接。
在開發階段,每件事都進行得很順利。但是,在部署的時候,開始失控。Dolly 的網絡管理員告訴她,她不能從她的桌面機訪問生產服務器或登臺服務器(staging server),所以她不得不爲每個部署階段開發不同的代碼版本。因爲這種情況,她需要一個新的 JDBC URL,所以還要爲測試、階段和生產進行獨立的部署。(一聽到要在每個環境中建立單獨部署,熟悉配置管理的人會戰戰兢兢的,但是既然這是種非常普遍的情況,所以他們也只好硬着頭皮上了。)
就在 Dolly 認爲通過不同的 URL 建立彼此獨立的部署已經解決了自己的配置問題時,她發現她的數據庫管理員不想在生產環境中運行 MySQL 實例。他說,MySQL 用作開發還可以,但是對於任務關鍵型數據而言,業務標準是 DB2®。現在她的構建不僅在數據庫 URL 方面有所不同,而且還需要不同的驅動程序。
事情越變越糟。她的應用程序非常有用,並且變得非常關鍵,以致於它從應用服務器那裏得到了故障恢復的能力,並被複制到 4 個服務器集羣。但是數據庫管理員提出了抗議,因爲她的應用程序的每個實例都要使用 64 個連接,而數據庫服務器總共只有 200 個可用連接 —— 全部都被 Dolly 的應用程序佔用了。更麻煩的是,DBA 已經確定 Dolly 的應用程序只需要 32 個連接,而且每天只有一個小時在使用。隨着她的應用程序規模擴大,應用程序遇到了數據庫級的爭用問題,而她的惟一選擇就是改變集羣的連接數量,而且還要做好準備,在集羣數量增長或者應用程序複製到另一個集羣時再重複一次這樣的操作。看來她已經決定了如何配置應用程序,應用程序的配置最好是留給系統管理員和數據庫管理員來做。
 
如果 Dolly 在開發應用程序時瞭解 J2EE 所扮演的角色,那麼她就可能避免遭遇這種困境。J2EE 規範把職責委託給多個開發角色:組件提供者(Component Provider)、應用程序組裝者(Application Assembler)、部署人員(Deployer)和系統管理員(System Administrator)。(在許多公司中,組件提供者和組件組裝者的角色是融合在一起的,部署人員和系統管理員的角色是融合在一起的。)在真正瞭解 J2EE 中的 JNDI 角色之前,掌握 J2EE 角色的作用非常重要。
組件提供者
這個角色負責創建 J2EE 組件,J2EE 組件可以是 Web 應用程序、企業級 JavaBean(EJB)組件,或者是應用程序客戶機(例如基於 Swing 的 GUI 客戶機應用程序)。組件提供者包括:HTML 設計師、文檔編程人員以及其他開發人員角色。大多數 J2EE 開發人員在組件提供者這一角色上耗費了相當多的時間。

應用程序組裝者
這個角色將多個 J2EE 模塊捆綁成一個彼此結合的、可以部署的整體:企業歸檔(EAR)文件。應用程序組裝者要選擇組件,分清它們之間的交互方式,配置它們的安全性和事務屬性,並把應用程序打包到 EAR 文件中。許多 IDE,例如 WebSphere® Studio、IDEA、JBuilder、WebLogic Workshop 和其他 IDE,都可以幫助應用程序組裝者以交互方式配置 EAR 文件。

部署人員(Deployer)
這個角色負責部署,這意味着將 EAR 安裝到 J2EE 容器(應用服務器)中,然後配置資源(例如數據庫連接池),把應用程序需要的資源綁定到應用服務器中的特定資源上,並啓動應用程序。

系統管理員(System Administrator)
這個角色負責保證容器需要的資源可用於容器。
假設有一個企業應用程序,該應用程序包含一個 Web 應用程序,還有一個負責業務邏輯和持久性的 EJB 組件。開發這個應用程序的組件供應商可能有許多,但是在許多情況下,可以由一個人來承擔全部職責。組件可以包含數據傳輸對象(一個 JAR 文件)、EJB 接口(另一個 JAR 文件)、EJB 實現本身(另一個 JAR 文件),以及用戶界面組件 —— servlet、JSP、HTML 頁面和其他靜態 Web 內容。用戶界面組件被進一步打包成 Web 應用程序,其中包含 servlet 類、JSP 文件、靜態內容,以及其他必需組件的 JAR(包括 EJB 接口)。
這聽起來好像用到的組件太多了,幾乎超出了人的想像範圍,尤其是在考慮構建一個典型的 Web 應用程序需要使用多少個 JAR 文件的時候。但是,重要的是認識到在這裏必須小心地管理依賴性。接口和傳輸對象是 Web 應用程序和 EJB 實現可以依賴的對象,但是依賴性的方向應該是相同的;還要避免產生循環依賴。J2EE 組件(例如 WAR 文件和 EJB JAR 文件)必須在它們的部署單元之外聲明它們在資源上的依賴性。
應用程序組裝者負責把 Web 應用程序中的依賴內容包含進來,並把它們整體打包成單個企業應用程序。工具在這裏幫助很大。IDE 可以協助創建反映模塊和 JAR 依賴性的項目結構,還允許您隨意指定包含或排除的模塊。
部署人員負責確保部署環境中存在組件所需的資源,並將組件綁定到平臺的可用資源上。例如,Web 應用程序中的外部 EJB 引用(部署描述符中的 ejb-ref)就是在此時被綁定到實際部署的 EJB 組件 —— 而且是立即綁定。
任何不平凡(nontrivial)的 J2EE 應用程序都需要訪問描述它期望使用環境的信息。這意味着開發和測試組件時,爲了臨時測試代碼,開發人員要承擔一些部署方面的職責。重要的是要理解:這麼做的時候,您就走出了開發人員的領域。否則,可以試着依靠 JDBC 驅動程序,或 URL、JMS 隊列名稱,或者其他具有無意識的、偶爾可能是災難性暗示的機器資源。
 
Dolly 的問題的解決方案是從她的應用程序中清除所有對數據存儲的直接引用。沒有對 JDBC 驅動程序的引用,沒有服務器名稱,沒有用戶名稱或口令 —— 甚至沒有數據庫池或連接管理。Dolly 需要編寫代碼來忽略將要訪問的特定外部資源,只需要知道其他人會提供使用這些外部資源所需的鏈接即可。這允許部署人員(任何處在這個角色的人)把數據庫連接分配給 Dolly 的應用程序。Dolly 沒有必要參與其中。(從數據庫安全性到遵守 Sarbanes-Oxley 法案,她都沒有參與進來,她這樣做也有充足的業務理由。)
許多開發人員知道:代碼和外部資源之間的緊密耦合是潛在的問題,但是在實踐中卻經常忘記角色的劃分。在小型開發工作中(指的是團隊規模或部署規模),即使忽視角色劃分也能獲得成功。(畢竟,如果應用程序只是個人的應用程序,而且您不準備依靠它,那麼把應用程序鎖定在特定的 PostgreSQL 實例上也挺好的。)
J2EE 規範要求所有 J2EE 容器都要提供 JNDI 規範的實現。JNDI 在 J2EE 中的角色就是“交換機” —— J2EE 組件在運行時間接地查找其他組件、資源或服務的通用機制。在多數情況下,提供 JNDI 供應者的容器可以充當有限的數據存儲,這樣管理員就可以設置應用程序的執行屬性,並讓其他應用程序引用這些屬性(Java 管理擴展(Java Management Extensions,JMX)也可以用作這個目的)。JNDI 在 J2EE 應用程序中的主要角色就是提供間接層,這樣組件就可以發現所需要的資源,而不用瞭解這些間接性。
現在我們重新來看一下 Dolly 的情況。在其簡單的 Web 應用程序中,她直接從應用程序代碼中使用了一個 JDBC 連接。參見清單 1,我們可以看出,Dolly 顯式地把 JDBC 驅動程序、數據庫 URL 以及她的用戶名和口令編碼到了 servlet 中:
 
清單 1. 典型(但是不好)的 JDBC 用法
        
Connection conn=null;
try {
    Class.forName("com.mysql.jdbc.Driver",
                                true, Thread.currentThread().getContextClassLoader());
    conn=DriverManager.getConnection("jdbc:mysql://dbserver?user=dolly&password=dagger");
    /* use the connection here */
    c.close();
}    
catch(Exception e) {
    e.printStackTrace();
}    
finally {
    if(conn!=null) {
        try {
            conn.close();
        } catch(SQLException e) {}
    }
}

如果不用這種方式指定配置信息,Dolly(以及她的同伴們)使用 JNDI 來查找 JDBC DataSource 會更好一些,如清單 2 所示:

清單 2. 使用 JNDI 得到數據源
        
Connection conn=null;
try {
    Context ctx=new InitialContext();
    Object datasourceRef=ctx.lookup("java:comp/env/jdbc/mydatasource");
    DataSource ds=(Datasource)datasourceRef;
    Connection c=ds.getConnection();
    /* use the connection */
    c.close();
}    
catch(Exception e) {
    e.printStackTrace();
}    
finally {
    if(conn!=null) {
        try {
            conn.close();
        } catch(SQLException e) { }
    }
}

爲了能夠得到 JDBC 連接,首先要執行一些小的部署配置,這樣我們纔可以在本地組件的 JNDI 下文中查找 DataSource。這可能有點煩瑣,但是很容易學。不幸的是,這意味着即使是爲了測試組件,開發人員也必須涉足部署人員的領地,並且還要準備配置應用服務器。
爲了讓 JNDI 解析 java:comp/env/jdbc/mydatasource 引用,部署人員必須把 <resource-ref> 標籤插入 web.xml 文件(Web 應用程序的部署描述符)。 <resource-ref> 標籤的意思就是“這個組件依賴於外部資源”。清單 3 顯示了一個示例:
<resource-ref>
    <description>Dollys DataSource</description>
    <res-ref-name>jdbc/mydatasource</res-ref-name>
    <res-ref-type>javax.sql.DataSource</res-ref-type>
    <res-auth>Container</res-auth>
</resource-ref>

    

 
<resource-ref> 入口告訴 servlet 容器,部署人員要在 組件命名上下文(component naming context) 中設置一個叫做 jdbc/mydatasource 的資源。組件命名上下文由前綴 java:comp/env/ 表示,所以完全限定的本地資源名稱是: java:comp/env/jdbc/mydatasource.
這隻定義了到外部資源的本地引用,還沒有創建引用指向的實際資源。(在 Java 語言中,類似的情況可能是: <resource-ref> 聲明瞭一個引用,比如 Object foo,但是沒有把 foo 設置成實際引用任何 Object。)
部署人員的工作就是創建 DataSource(或者是創建一個 Object 對象,讓 foo 指向它,在我們的 Java 語言示例中就是這樣)。每個容器都有自己設置數據源的機制。例如,在 JBoss 中,是利用服務來定義數據源(請參閱 $JBOSS/server/default/deploy/hsqldb-ds.xml,把它作爲示例),它指定自己是 DataSource 的全局 JNDI 名稱(默認情況下是 DefaultDS)。在創建資源之後,第三步仍然很關鍵:把資源連接或者綁定到應用程序組件使用的本地名稱。在使用 Web 應用程序的情況下,是使用特定於供應商的部署描述符擴展來指定這個綁定,清單 4 中顯示了一個這樣的例子。(JBoss 用稱爲 jboss-Web.xml 的文件作爲特定於供應商的 Web 應用程序部署描述符。)

清單 4. 用特定於供應商的部署描述符將資源綁定到 JDI 名稱
        
<resource-ref>
   <res-ref-name>jdbc/mydatasource</res-ref-name>
   <jndi-name>java:DefaultDS</jndi-name>
</resource-ref>
      

這表明應該將本地資源引用名稱( jdbc/mydatasource)映射到名爲 java:DefaultDS 的全局資源。如果全局資源名稱出於某種原因發生了變化,而應用程序的代碼無需變化,那麼只需修改這個映射即可。在這裏,有兩個級別的間接尋址:一個定義並命名資源( java:DefaultDS),另一個把特定於本地組件的名稱( jdbc/mydatasource)綁定到命名的資源。(實際上,當您在 EAR 級別上映射資源時,可能還存在第三級別的間接尋址。)
當然,J2EE 中的資源並不侷限於 JDBC 數據源。引用的類型有很多,其中包括資源引用(已經討論過)、環境實體和 EJB 引用。特別是 EJB 引用,它暴露了 JNDI 在 J2EE 中的另外一項關鍵角色:查找其他應用程序組件。
試想一下這種情況:當一家公司從 Order Ontology Processing Services(OOPS)購買了一個可部署的 EJB 組件來處理客戶訂單時,會發生什麼。爲了便於舉例說明,我們把它叫做 ProcessOrders V1.0。ProcessOrders 1.0 有兩部分:一組接口和支持類(home 和 remote 接口,以及支持的傳輸類);實際 EJB 組件自身。選擇 OOPS 是因爲它在這個領域的專業性。
該公司遵照 J2EE 規範,編寫使用 EJB 引用的 Web 應用程序。公司的部署人員把 ProcessOrders 1.0 綁定到 JNDI 樹中,將它用作 ejb/ProcessOrders/1.0,並解析 Web 應用程序的資源名稱,以指向這個全局 JNDI 名稱。目前爲止,這些都是 EJB 組件非常普通的用法。但是,當我們考慮公司的開發週期與公司供應商之間的交互時,事情就變得複雜起來。在這裏,JNDI 也能幫助我們。
我們假設 OOPS 發佈了一個新版本,即 ProcessOrders V1.1。這個新版本有一些新功能,公司內部的一個新應用程序需要這些新功能,而且很自然地擴展了 EJB 組件的業務接口。
在這裏,公司有以下幾個選擇:可以更新所有應用程序來使用新版本,也可以編寫自己的版本,或者使用 JNDI 的引用解析,允許每個應用程序在不影響其他應用程序的情況下使用自己的 EJB 組件版本。立刻更新所有應用程序對維護來說是一場噩夢,它要求對所有組件都進行完整的迴歸測試,這通常是一項艱鉅的任務,而且如果發生任何功能測試失敗的話,那麼還要進行另一輪調試。
編寫內部(in-house)組件常常是沒有必要的重複工作。如果組件是由在這個業務領域內具有專業知識的公司編寫的,那麼給定的 IT 商店不可能像專業的組件供應商那樣精通業務功能。
正如您可能已經猜到的那樣,最好的解決方案是用 JNDI 解析。EJB 的 JNDI 引用非常類似於 JDBC 資源的引用。對於每個引用,部署人員都需要把新組件按特定的名稱(比如說 ejb/ProcessOrders/1.1)綁定到全局樹中,對於需要 EJB 組件的其他每個組件,還要爲組件在部署描述符中解析 EJB 引用。依賴於 V1.0 以前的應用程序不需要進行任何修改,也不需要重新測試,這縮短了實現的時間、降低了成本並減少了複雜性。在服務趨於轉換的環境中,這是一種很有效的方法。可以對應用程序架構中所得到的所有組件進行這類配置管理,從 EJB 組件到 JMS 隊列和主題,再到簡單配置字符串或其他對象,這可以降低隨時間的推移服務變更所產生的維護成本,同時還可以簡化部署,減少集成工作。
 
有一個古老的計算機科學笑話:每個編程問題都可以僅僅用一個抽象層(或間接的)來解決。在 J2EE 中,JNDI 是把 J2EE 應用程序合在一起的粘合劑,但還沒有緊到無法讓人很容易地把它們分開並重新裝配。JNDI 提供的間接尋址允許跨企業交付可伸縮的、功能強大且很靈活的應用程序。這是 J2EE 的承諾,而且經過一些計劃和預先考慮,這個承諾是完全可以實現的。實際上,它要比許多人想像的容易得多。
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章