把您從麻煩中解脫html
掌握 J2EE 是件使人生畏的事,由於它包含的技術和縮略語在不斷地增加。Java 命名和目錄接口(Java Naming and Directory Interface,JNDI)從一開始就一直是 Java 2 平臺企業版(JEE)的核心,可是 J2EE 開發新手常常用很差它。本文將消除 JNDI 在 J2EE 應用程序中所扮演角色的神祕性,並展現它如何幫助應用程序從部署細節中解脫出來。java
雖然 J2EE 平臺提升了普通企業開發人員的生活水平,可是這種提升是以不得不學習許多規範和技術爲代價的,這些規範和技術則是 J2EE 爲了成爲無所不包的分佈式計算平臺而整合進來的。Dolly Developer 是衆多開發人員中的一員,她已經發現了一個特性,該特性有助於緩解隨企業級應用程序部署而帶來的負擔,這個特性就是 JNDI,即 Java 命名與目錄接口(Java Naming and Directory Interface)。讓咱們來看看 Dolly 在沒有 JNDI 的時候是怎麼作的,以及她是如何正確地應用 JNDI 來改善其情況的。mysql
Dolly Developer 正在編寫使用 JDBC 數據源的 Web 應用程序。她知道本身正在使用 MySQL,因此她將一個對 MySQL JDBC 驅動程序類的引用進行了編碼,並經過使用適當的 JDBC URL 鏈接到其 Web 應用程序中的數據庫。她認識到數據庫鏈接池的重要性,因此她包含了一個鏈接池包,並把它配置成最多使用 64 個鏈接;她知道數據庫服務器已經被設置成最多容許 128 臺客戶機進行鏈接。web
在開發階段,每件事都進行得很順利。可是,在部署的時候,開始失控。Dolly 的網絡管理員告訴她,她不能從她的桌面機訪問生產服務器或登臺服務器(staging server),因此她不得不爲每一個部署階段開發不一樣的代碼版本。由於這種狀況,她須要一個新的 JDBC URL,因此還要爲測試、階段和生產進行獨立的部署。(一聽到要在每一個環境中創建單獨部署,熟悉配置管理的人會戰戰兢兢的,可是既然這是種很是廣泛的狀況,因此他們也只好硬着頭皮上了。)sql
就在 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 角色的做用很是重要。安全
假設有一個企業應用程序,該應用程序包含一個 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 中:
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 所示:
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 應用程序部署描述符。)
<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 的承諾,並且通過一些計劃和預先考慮,這個承諾是徹底能夠實現的。實際上,它要比許多人想像的容易得多。
原文:http://www.ibm.com/developerworks/cn/java/j-jndi/index.html