走出 JNDI 迷宮
從單機編程轉向 EJB 技術和分布式計算這些更復雜領域的 Java 開發人員常常會陷入困境:編寫成功地游歷 JDNI 迷宮的代碼會很困難,多計算機和配置也增加了出錯的可能性。在本文中,EJB開發人員 Daniel Would 解釋了如何編寫可以成功地找到在 JNDI 名稱空間中發布的 EJB 組件的客戶代碼。他向您展示了使處理更容易的各種編程選項,并提供了一些可以在您自己的應用程序中作為實用工具類使用的代碼。
如果您在讓客戶機應用程序看到 EJB 組件這一方面從來都沒有任何問題,并且認為在 Java 平臺的不同安裝上、或者在完全不同的計算機上運行您的客戶機和 bean 一點也不復雜,那么本文可能不會吸引您。不過,如果您才剛剛開始,并且在試圖用真實的配置做任何事情時看見彈出了許多奇怪和意義不明的錯誤消息,那么就請讀下去。
您已經在自己所鐘愛的 Java 書籍中讀過了關于企業 Javabean 技術的那一章,也已經練習過了簡單的 HelloWorld bean,并遵循所建議的部署過程發布了它。現在您得編寫一個客戶機,以便通過這個客戶機來調用這個杰作。因此您寫出了類似清單 1 中的代碼:
清單 1. 一個調用 bean 的非常簡單的客戶機
|
在命令行中運行這個客戶機,使用手頭最方便的一個 Java 安裝 ―― 即應用服務器使用的那一個。所有事情都很完美!帶著成功的喜悅,您轉移到第二臺計算機上運行您的客戶機。這回,您得到了一個可怕的錯誤消息。首先,您可能得到
java.lang.NoClassDefFoundError: javax/ejb/EJBObject
,然后是一大堆其他的
NoClassDefFoundError
s,因為您忘記提交一個帶有必需的 stub 和 tie 的 JAR 文件,并且沒有提供或者考慮到其他各種
EJB 相關的內容。不過最終,您的客戶機運行到了第一行有意思的代碼(
InitialContext ic = new InitialContext();
)。在到達這一行時得到的異常
―― 您幾乎肯定會得到一個異常 ―― 將會根據您所選擇的特定
上下文 provider而有所不同。
![]() |
|
在我們繼續往下之前,定義幾個術語會很有幫助。計算世界使用的都是一些奇怪的術語、時髦的語匯和首字母縮寫詞,Java 技術也不例外(也許這應該是
JavaIsNoException
?)。如果您遇到了上面所說的問題,那么這里面的術語可能會讓您感到有些無所適從。所以讓我們討論在本文中將會遇到的術語,搞明白它們的意思是一個好主意。
home接口(home interface)和遠程接口(remote interface) 企業 JavaBean 組件有三個部分。首先是 bean 代碼本身。然后是 home接口,它定義了創建您自己的 EJB bean 的方法。home接口是在名稱空間中發布的。當您有了home接口后,就可以調用
Create()
以從應用服務器獲得遠程接口。獲得了遠程接口后,就可以調用構成實際的
EJB 代碼的方法了。
如何將這些術語應用到您的城鎮模擬中去呢?到達正確的城鎮并找到正確的地址后,您需要走進商店或者按鈴(調用
Create()
)。這個過程對于您要去的所有商店都是一樣的,不過,您所收到的響應取決于是由誰來提供服務
―― 比如是一位屠夫、一位面包師還是一位燭臺制作者。這個響應代表了
遠程接口。每個人都是不同的并且可以要求他提供不同的東西。您必須知道與您交談的人(即
bean)的職業才能提出正確的問題(即調用正確的方法) ―― 向一位屠夫要一條面包可不妥當。
CosNaming、LDAP 和 JNDI Java 命名和目錄接口(Java Naming and Directory Interface JNDI)提供了一個標準接口,它指明您需要如何與名稱空間交互。我們所提到的 LDAP和 CosNaming 就是 JDNI 名稱空間類型。現在擴展我們的比喻:JNDI 是城鎮的模板,而 CosNaming 和 LDAP 是特定的城鎮。它們以相似的方式操作,但是有不同的布局。
![]() |
|
讓我們看一看如何使用所有這些元素以成功地從遠程計算機上調用我們的 EJB 組件上的方法。為了讓客戶程序連接到您精心打造的 EJB 組件,需要幾樣東西。首先,它需要客戶代碼的所有 JAR 文件、一般性的 EJB 相關 JAR 文件如 J2EE.jar 以及在部署 bean 時生成的 stub 和 tie。這些文件讓您的客戶機可以一直到達初始上下文。
接下來您的客戶機需要的信息是一些屬性的值。首先,您將需要幾個
java.naming.factory.initial
的值。該屬性指向一個提供初始上下文工廠的類。該屬性的一個典型值是
com.sun.jndi.cosnaming.CNCtxFactory
,這也是我們在這里的幾個例子中所使用的值。這個類存在于
rt.jar
中,因而它是基本 JVM 的一部分。工廠是由 CosNaming 命名服務器所使用的,但是 JVM
還包括一個 LDAP 工廠。我們在后面將會看到,不同的應用服務器提供它們自己的初始上下文工廠。
這個類連同命名服務器 URL 和端口號的詳細信息,用于生成與名稱空間交互的
InitialContext
類。不過,如果沒有
provider URL,那么它將連接到
localhost
的 900 端口(或者您的上下文工廠的其他默認端口)。要連接到遠程服務器,您需要有屬性
java.naming.provider.url
的一個值。
新程序員對于所有這些覺得很難理解的原因是:不管您在應用服務器本地運行任何東西,這東西通常都會聽話地工作。這是由于環境照管了一切,當您要求一個
InitialContext
時,環境就會給您提供您想要的那個。但是當您將客戶即轉移到不同的計算機上時,就得靠自己了。您需要知道拷貝哪一個 JAR 文件,以及要做哪些設置。我知道有些人為使他們的客戶機正確工作,將應用服務器上的所有 JAR 文件都拷貝到第二臺計算機上!
在默認情況下,
InitialContext
工廠是在
jndi.properties
中定義的,這個工廠類有默認的服務器 URL 和端口號默認值。這個文件在類路徑中(這一般意味著在本地目錄)或者在您的類路徑中的任何 JAR 中。不同的應用服務器可能在不同的
JAR 文件中提供它們的默認值,WebSphere Application Server 在
namingclient.jar
中儲存一個默認副本。要指定您自己的默認值,只需要編輯在類路徑中的第一個副本。這是配置屬性的一種方法,如果缺少命令行或者代碼驅動的設置,那么客戶機將使用
jndi.properties
中的值。不過,雖然這可能適合于簡單的設置,但是如果處理多個服務器和名稱空間,那么您可能希望一個客戶一個客戶地進行配置。
這些屬性是如何根據我們要使用的名稱空間而使用不同的值的呢?正如前面提到的,有兩種形式的 JNDI 名稱空間:CosNaming 和 LDAP。其中每一個都有與之相關聯的傳輸:分別是
IIOP 和 LDAP。一個 LDAP 名稱空間使用 LDAP 傳輸(您將用一個像
ldap://myldapnameserver
這樣的 URL 連接到它),而 CosNaming 使用一個 IIOP 傳輸(您將用一個像
iiop://mycosnamingserver
這樣的 URL 連接到它)。CosNaming 的默認端口號是 900,而 LDAP 的默認端口號是 389。不過,任何給定的名稱空間服務器實現使用的默認值可能是不同的。
讓我們看一下如何用命令行配置屬性。如果您要在家里自己練習,進入 JDK 安裝中的
bin
目錄。在這個文件夾中,可以找到一個名為
tnameserv.exe
的程序(對于 Windows)或者只是
tnameserv
(對于基于 UNIX 的系統)。通過執行這個程序將會在端口 900 啟動一個示例 CosNmaing 命名服務器。
現在正好可以用一個可以查看 CosNaming 名稱空間的實用工具來裝備您自己。我本人使用 Eclipse 作為開發環境,我在下面的 參考資料 部分中提供了到 JNDI 瀏覽器插件的鏈接。理論上,您應該可以將一個名稱空間瀏覽器指向自己計算機的端口 900,并看到一個非常無聊的空名稱空間(盡管一些應用服務器在默認情況下會用很多不同的內容填充名稱空間)。為了豐富我們的名稱空間,我們現在將編寫一 個簡單的程序以在它里面放一些內容,如清單 2 所示:
清單 2. 一個簡單的 cosNaming 名稱空間交互
|
這個應用程序將假定為得到正確的初始上下文件所需的所有屬性都是可用的。所以現在可以從命令行運行它并在運行時提供這些屬性(其中 URL 要根據您的環境作調整):
|
一切正常,我們的客戶會找到示例名稱空間的上下文并創建名為
Test
的子上下文。您可以用名稱空間瀏覽器確認這一點。
現 在試著在一臺計算機上運行命名服務器,用同一個命令行(當然,對 URL 再次做了調整)在另一臺計算機上運行清單 2 中的應用程序。它運行起來應該沒有問題(您可能需要修改這個例子以改變所限定的內容,甚至刪除子上下文而不是創建它,這樣在第二次運行時您就可以確信它已 經起過作用了)。
那么,如果不希望在命令行中設置這些屬性怎么辦?還有另外一個方法。可以在程序中顯式地聲明這些屬性。這意味著您不需要為
java
命令提供特殊的選項。改變清單 2 中的代碼以顯式地設置所需要的屬性后,它看起來與清單 3 中的代碼一樣:
清單 3. 簡單的 cosNaming 名稱空間交互,在應用程序代碼中設置屬性
|
現在這個程序不再需要長長的命令行配置,不過要記住,以這種方式編寫的應用程序硬編碼了這些設置。
![]() |
|
到目前為止,我們已經看到了幾個可以證明我們已連接到遠程名稱空間并完成一些任務的例子,盡管這些任務是相當無聊的 ―― 創建一個子上下文。在實際中,一般是由工具來為您完成所有的創建和發布工作,您
真正 需要的做是查找一個對象。在這一節,我們將在
CosNaming 名稱空間中獲得已發布的 HelloWorld bean 的
Home
接口。然后我們再看一下如何在
LDAP 名稱空間中找到它的
Home
接口。
為了說明問題,我們假設您已經部署了 HelloWorld bean,它的home接口
HelloWorldHome
發布在
example/HelloWorldHome
(如果您只想試一試,但是又不想自己創建一個 HellowWorld
bean,那么在
參考資料中有一個下載預打包的 bean JAR 文件的鏈接以及一個使用它的客戶機的文件)。
![]() |
|
在上一節,我們進行了連接到命名服務器的艱苦工作,現在我們所需要的就只是查詢 EJB 組件了。這需要我們向查詢方法傳遞一個字符串,它表示從
InitialContext
(您在城鎮中的出發點)到想要去的
HomeInterface
(房屋或者商店)的方向。聽起來簡單 ―― 但是這里您所選擇的特定上下文工廠就要產生影響了。像
WebSphere 這樣的應用服務器所帶的工廠類并不總是把您放到名稱空間的根上。所以我們為了查詢
HomeInterface
而需要的字符串會根據
InitialContext
將您所放到城鎮中的位置而變化。并且,在本地服務器上,上下文工廠可能將您放到與在遠程服務器上不同的起始位置。
因 為這個原因,我建議您不要像在清單 3 中那樣硬編碼所使用的查詢字符串,而是用命令行或者屬性文件傳遞。特別是對于具有多步的體系結構更應如此。例如,您可能有一個調用一個 EJB 組件的客戶機,這個 bean 可能又需要調用也許是在不同的服務器上的第二個 EJB 組件!在這種情況下,屬性應該在每一步中傳遞。這為反復實驗(trial-and-error)查詢提供了一種簡單的機制,并且只需要相對較少的改變就可 以得到最終應用程序的靈活性。因此讓我們看一個示例查詢應用程序。在清單 4 中,屬性是在程序中設置的,但是它又以命令行值為依據。這樣命令行與在我們前一個例子中使用的稍有不同,如我們在下面所看到的。
清單 4. 查詢一個home接口
|
這個程序是用三個參數調用的:要使用的上下文工廠、provider URL 和包含要查詢的名字的字符串。我們已經知道前兩個是什么,那么第三個呢?
如果您仍然使用
tnameserv
作為命名服務器,那么您很可能將 bean 直接發布到
/example/HelloWorldHome
。在這種情況下,只要將
/example/HelloWorldHome
作為第三個參數傳遞就可以進行成功的查詢。不過,如果您使用的命名服務器有一個更復雜的命名空間,那么可能會存在由所使用的部署工具增加的額外的層。例如,WebSphere
在默認情況下將 JavaBean 部署到
ejb/
,但是這不是名稱空間的根,并且只有當使用 WebSphere 的上下文工廠時,通過傳入字符串
/ejb/example/HelloWorldHome
才會使您處于名稱空間中的正確位置。 如果您使用一個與應用服務器提供的不同的上下文工廠(例如在一臺只有標準
Java 安裝的計算機上運行客戶機時就需要這樣做)時,這個問題會更加惡化。不過,應用服務器的命名服務器文檔應當說明在查詢 EJB 組件時將會從名稱空間的什么地方開始。看一下文檔中的例子,再用瀏覽器查看名稱空間以確定其客戶機的
InitialContext
會將它們放到什么地方。名稱空間往往會循環,這樣您就可以試著沿著一個分枝到無限。這意味著從最開始的上下文可以找到一條回家的道路。
總之,下面是傳遞相應參數給清單 4 中的應用程序的命令行:
|
在 CosNaming 中,名稱空間子上下文由斜線(/)字符分隔,這與標準 URL 一樣。LDAP 的語法則不同,我們在下面將會看到。
現在讓我們再加上 LDAP。就本文的內容來講,LDAP 是另一個 JNDI 名稱空間,但是它的結構的表示方法與 CosNaming 名稱空間的表示方法截然不同。它還需要一個不同的上下文工廠 ―― 但是這不成問題,因為我們總會在命令行指定正確的工廠(在我們的例子中,我們將使用屬于基本 JVM 一部分的工廠,但是要記住不同的應用服務器可能有自己的工廠)。并且它需要一個指向不同命名服務器的指針 ―― 并且,幸運的是,我們也是在命令行中指定它的。當然,表示home接口位置的字符串是不同的,但是您猜如何?是的,我們還是在命令行中指定它。您可以看到使用這些命令行調用的好處:所有要做的只是改變調用我們的測試程序的方式,理論上我們可以到達任何 JDNI 命名服務器,甚至可以順利地從 CosNaming 轉移到 LDAP 而不用改變任何代碼。是的,這是只是理論,當然無論如何,關鍵是要有正確的參數。
一些命名服務器會保護部分命名空間,這意味著只能發布到允許的區域。假設您有一個運行的 LDAP 服務器,它的細節如下:
- URL:
ldap://mymachine:1389
- BaseDN:
c=myldap
LDAP 名稱空間中的樹結構一般來說像下面這樣:
|
不過,當我們將這個字符串傳遞給程序時,我們需要反轉它(不要問我為什么)。所以我們使用的字符串看起來是這樣的:
|
BaseDN 表示在名稱空間中您希望開始的位置。對于給定的 LDAP 命名服務器來說這可以是很多位置,這取決它是如何構造的。在這個例子中,我們直接到
c=myldap
的根。但是如果我們希望跳到名稱空間中的一個樹,那么可以指定
ibm-wsnTree=myTree,c=myldap
作為 BaseDN 而不是跳到那一點。
這樣,我們將傳遞給程序的命令行參數就像下面這樣:
|
![]() |
|
這里我們指定一個 LDAP 上下文工廠 ,然后傳遞 LDAP 服務器的名字以及我們想要開始的位置。然后是到要查詢的 EJB 組件的反轉的路徑。我們可以用這個命令行調用在 CosNaming 例子中使用的同一段代碼( 清單 4)。
當然,本文中使用的代碼沒有理由不能構成一個助手類的一個方法 ―― 它帶三個參數,并且返回
Object or
,這個對象是在試圖做任何事情之前調用
ic.lookup(args[2])
時返回的。然后,當您需要進行一次查詢時,只需使用這個助手類,向它傳遞適合于當前情況的適當參數,取回您所需要的對象引用,并準備將它窄化到實際的類。(
注意:我不保證這種類的性能,而只是提供這段原本就是如此的代碼,我或者
IBM 對此不作任何保證。)當然,可以通過反射實現一種完全通用的方式,但這會使事情復雜得多,也超出了本文的范圍。
在 我們結束之前還有最后一件事要考慮。您可以編寫一個結合了我們在清單 3 和 4 中使用的技術的客戶機。它會檢查命令行中是否給出了一個值;如果有,它就設置這些值,如果沒有,它就使用硬編碼的值。這樣,在程序中可以有有意義的默認 值,但是如果需要,也可以用命令行選項覆蓋它們。只需要對代碼進行微不足道的更改。
![]() |
|
作為回顧:下面是在有多個 EJB 查詢和多個應用服務器的情況下,要使任何系統運行而應該有或者應該知道的最重要的四件事情:
- 在任何給定階段,下一階段的所有 stub 和 tie 都必須在類路徑上。除非環境知道這個類是什么樣的,否則您不能窄化一個對象以使用它。
- 每一階段都需要與 EJB 相關的一般性 JAR 文件,如
J2EE.jar
。
- 以參數的形式傳遞上下文工廠類型、命名服務器名和 JDNI 查詢字符串。以便能夠輕松順應變化。
- 知道您的名稱空間。記住您的 JDNI 查詢字符串需要將您從在名稱空間中開始的位置移動到您的對象所儲存的位置。但是您并不總是在同一位置開始!用一個工具瀏覽名稱空間,并了解在本地和遠程查詢中是從什么位置開始的。
對于習慣于編寫全部在同一臺計算機上執行的代碼的開發人員來說,瀏覽遠程名稱空間可能是一個困難的過程。希望本文的提示和代碼可以幫助您設置并運行您的分布式 EJB 應用程序。當您掌握了 JNDI 名稱空間后,再去看一下 developerWorks 上由 Brett McLaughlin 所寫的 EJB 最佳實踐系列(請參閱 參考資料),以獲得用于優化代碼的一些很棒的技巧。
![]() |
|
- 您可以參閱本文在 developerWorks 全球站點上的
英文原文.
- 下載
helloworldejb.jar,這是我們的例子中使用的簡單
bean。可以用
HWClient.jar
作為客戶機。
- 如果您使用 Eclipse 或者 WebSphere Studio Application Developer,那么可以使用
JNDI
Explorer插件瀏覽名稱空間。
- 了解更多關于
Eclipse和
WebSphere
Application Developer Studio 開發環境的內容。WebSphere Studio 是建立于 Eclipse
2.1 之上的。
- 使用正確的工具會使您輕松得多。alphaWorks 有
幾個用于
Eclipse 的插件。
- 從 WebSphere Application Server 庫的“
Developing
applications that use JNDI”一文中,可以了解如何開發使用 WebSphere Application Server 的 JNDI 的應用程序。
- Brett McLaughlin 在 developerWorks 的
EJB 最佳實踐
專欄對于那些開始進行 EJB 編程的人來說是一個很好的資源。
- 如果您確實希望了解 EJB 編程的細節,那么 Joe Sam Shirah 的綜合教程“
Enterprise JavaBean 基礎”(
developerWorks,2003
年 4 月)可以為您提供堅實的基礎。
- 在
developerWorks
Java技術專區
,可以找到數百篇有關 Java 編程各方面的文章。
![]() |
|
![]() | |
Daniel Would 于 2001 年加入 IBM。他做了一年 CTG 系統測試員,之后成為 CICS 系統測試員一直到現在。在 IBM 時,Daniel 的工作集中于 Java 技術和 EJB 組件。可以通過 wouldd@uk.ibm.com 與 Daniel 聯系。 |
posted on 2006-02-16 10:32 靜夜思 閱讀(384) 評論(0) 編輯 收藏 所屬分類: j2ee基礎