<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    GHawk

    #

    EJB 異常處理的最佳做法

    http://www-128.ibm.com/developerworks/cn/java/j-ejbexcept/

    EJB 異常處理的最佳做法

    學習在基于 EJB 的系統(tǒng)上編寫可以更快解決問題的代碼

    developerWorks
    文檔選項
    將此頁作為電子郵件發(fā)送

    將此頁作為電子郵件發(fā)送

    未顯示需要 JavaScript 的文檔選項


    對此頁的評價

    幫助我們改進這些內(nèi)容


    級別: 初級

    Srikanth Shenoy, J2EE 顧問

    2002 年 5 月 05 日

    隨著 J2EE 成為企業(yè)開發(fā)平臺之選,越來越多基于 J2EE 的應用程序?qū)⑼度肷a(chǎn)。J2EE 平臺的重要組件之一是 Enterprise JavaBean(EJB)API。J2EE 和 EJB 技術(shù)一起提供了許多優(yōu)點,但隨之而來的還有一些新的挑戰(zhàn)。特別是企業(yè)系統(tǒng),其中的任何問題都必須快速得到解決。在本文中,企業(yè) Java 編程老手 Srikanth Shenoy 展現(xiàn)了他在 EJB 異常處理方面的最佳做法,這些做法可以更快解決問題。

    在 hello-world 情形中,異常處理非常簡單。每當碰到某個方法的異常時,就捕獲該異常并打印堆棧跟蹤或者聲明這個方法拋出異常。不幸的是,這種辦法不足以處理現(xiàn)實中出現(xiàn)的各種類型的異常。在生產(chǎn)系統(tǒng)中,當有異常拋出時,很可能是最終用戶無法處理他或她的請求。當發(fā)生這樣的異常時,最終用戶通常希望能這樣:

    • 有一條清楚的消息表明已經(jīng)發(fā)生了一個錯誤
    • 有一個唯一的錯誤號,他可以據(jù)此訪問可方便獲得的客戶支持系統(tǒng)
    • 問題快速得到解決,并且可以確信他的請求已經(jīng)得到處理,或者將在設定的時間段內(nèi)得到處理

    理想情況下,企業(yè)級系統(tǒng)將不僅為客戶提供這些基本的服務,還將準備好一些必要的后端機制。舉例來說,客戶服務小組應該收到即時的錯誤通知,以便在客戶打電話求助之前服務代表就能意識到問題。此外,服務代表應該能夠交叉引用用戶的唯一錯誤號和產(chǎn)品日志,從而快速識別問題 ― 最好是能把問題定位到確切的行號或確切的方法。為了給最終用戶和支持小組提供他們需要的工具和服務,在構(gòu)建一個系統(tǒng)時,您就必須對系統(tǒng)被部署后可能出問題的所有地方心中有數(shù)。

    在本文中,我們將談談基于 EJB 的系統(tǒng)中的異常處理。我們將從回顧異常處理的基礎(chǔ)知識開始,包括日志實用程序的使用,然后,很快就轉(zhuǎn)入對 EJB 技術(shù)如何定義和管理不同類型的異常進行更詳細的討論。此后,我們將通過一些代碼示例來研究一些常見的異常處理解決方案的優(yōu)缺點,我還將展示我自己在充分利用 EJB 異常處理方面的最佳做法。

    請注意,本文假設您熟悉 J2EE 和 EJB 技術(shù)。您應理解實體 bean 和會話 bean 的差異。如果您對 bean 管理的持久性(bean-managed persistence(BMP))和容器管理的持久性(container-managed persistence(CMP))在實體 bean 上下文中是什么意思稍有了解,也是有幫助的。請參閱 參考資料部分了解關(guān)于 J2EE 和 EJB 技術(shù)的更多信息。

    異常處理基礎(chǔ)知識

    解決系統(tǒng)錯誤的第一步是建立一個與生產(chǎn)系統(tǒng)具有相同構(gòu)造的測試系統(tǒng),然后跟蹤導致拋出異常的所有代碼,以及代碼中的所有不同分支。在分布式應用程序中,很可能是調(diào)試器不工作了,所以,您可能將用 System.out.println() 方法跟蹤異常。 System.out.println 盡管很方便,但開銷巨大。在磁盤 I/O 期間, System.out.println 對 I/O 處理進行同步,這極大降低了吞吐量。在缺省情況下,堆棧跟蹤被記錄到控制臺。但是,在生產(chǎn)系統(tǒng)中,瀏覽控制臺以查看異常跟蹤是行不通的。而且,不能保證堆棧跟蹤會顯示在生產(chǎn)系統(tǒng)中,因為,在 NT 上,系統(tǒng)管理員可以把 System.outSystem.err 映射到 ' ' ,在 UNIX 上,可以映射到 dev/null 。此外,如果您把 J2EE 應用程序服務器作為 NT 服務運行,甚至不會有控制臺。即使您把控制臺日志重定向到一個輸出文件,當產(chǎn)品 J2EE 應用程序服務器重新啟動時,這個文件很可能也將被重寫。

    異常處理的原則

    以下是一些普遍接受的異常處理原則:

    1. 如果無法處理某個異常,那就不要捕獲它。
    2. 如果捕獲了一個異常,請不要胡亂處理它。
    3. 盡量在靠近異常被拋出的地方捕獲異常。
    4. 在捕獲異常的地方將它記錄到日志中,除非您打算將它重新拋出。
    5. 按照您的異常處理必須多精細來構(gòu)造您的方法。
    6. 需要用幾種類型的異常就用幾種,尤其是對于應用程序異常。

    第 1 點顯然與第 3 點相抵觸。實際的解決方案是以下兩者的折衷:您在距異常被拋出多近的地方將它捕獲;在完全丟失原始異常的意圖或內(nèi)容之前,您可以讓異常落在多遠的地方。

    :盡管這些原則的應用遍及所有 EJB 異常處理機制,但它們并不是特別針對 EJB 異常處理的。

    由于以上這些原因,把代碼組裝成產(chǎn)品并同時包含 System.out.println 并不是一種選擇。在測試期間使用 System.out.println ,然后在形成產(chǎn)品之前除去 System.out.println 也不是上策,因為這樣做意味著您的產(chǎn)品代碼與測試代碼運行得不盡相同。您需要的是一種聲明控制日志機制,以使您的測試代碼和產(chǎn)品代碼相同,并且當記錄日志以聲明方式關(guān)閉時,給產(chǎn)品帶來的性能開銷最小。

    這里的解決方案顯然是使用一個日志實用程序。采用恰當?shù)木幋a約定,日志實用程序?qū)⒇撠熅_地記錄下任何類型的消息,不論是系統(tǒng)錯誤還是一些警告。所以,我們將在進一步講述之前談談日志實用程序。

    日志領(lǐng)域:鳥瞰

    每個大型應用程序在開發(fā)、測試及產(chǎn)品周期中都使用日志實用程序。在今天的日志領(lǐng)域中,有幾個角逐者,其中有兩個廣為人知。一個是 Log4J,它是來自 Apache 的 Jakarta 的一個開放源代碼的項目。另一個是 J2SE 1.4 捆綁提供的,它是最近剛加入到這個行列的。我們將使用 Log4J 說明本文所討論的最佳做法;但是,這些最佳做法并不特別依賴于 Log4J。

    Log4J 有三個主要組件:layout、appender 和 category。 Layou代表消息被記錄到日志中的格式。 appender是消息將被記錄到的物理位置的別名。而 category則是有名稱的實體:您可以把它當作是日志的句柄。layout 和 appender 在 XML 配置文件中聲明。每個 category 帶有它自己的 layout 和 appender 定義。當您獲取了一個 category 并把消息記錄到它那里時,消息在與該 category 相關(guān)聯(lián)的各個 appender 處結(jié)束,并且所有這些消息都將以 XML 配置文件中指定的 layout 格式表示。

    Log4J 給消息指定四種優(yōu)先級:它們是 ERROR、WARN、INFO 和 DEBUG。為便于本文的討論,所有異常都以具有 ERROR 優(yōu)先級記錄。當記錄本文中的一個異常時,我們將能夠找到獲取 category(使用 Category.getInstance(String name) 方法)的代碼,然后調(diào)用方法 category.error() (它與具有 ERROR 優(yōu)先級的消息相對應)。

    盡管日志實用程序能幫助我們把消息記錄到適當?shù)某志梦恢茫鼈儾⒉荒芨龁栴}。它們不能從產(chǎn)品日志中精確找出某個客戶的問題報告;這一便利技術(shù)留給您把它構(gòu)建到您正在開發(fā)的系統(tǒng)中。

    要了解關(guān)于 Log4J 日志實用程序或 J2SE 所帶的日志實用程序的更多信息,請參閱 參考資料部分。

    異常的類別

    異常的分類有不同方式。這里,我們將討論從 EJB 的角度如何對異常進行分類。EJB 規(guī)范將異常大致分成三類:

    • JVM 異常:這種類型的異常由 JVM 拋出。 OutOfMemoryError 就是 JVM 異常的一個常見示例。對 JVM 異常您無能為力。它們表明一種致命的情況。唯一得體的退出辦法是停止應用程序服務器(可能要增加硬件資源),然后重新啟動系統(tǒng)。
    • 應用程序異常:應用程序異常是一種定制異常,由應用程序或第三方的庫拋出。這些本質(zhì)上是受查異常(checked exception);它們預示了業(yè)務邏輯中的某個條件尚未滿足。在這樣的情況下,EJB 方法的調(diào)用者可以得體地處理這種局面并采用另一條備用途徑。
    • 系統(tǒng)異常:在大多數(shù)情況下,系統(tǒng)異常由 JVM 作為 RuntimeException 的子類拋出。例如, NullPointerExceptionArrayOutOfBoundsException 將因代碼中的錯誤而被拋出。另一種類型的系統(tǒng)異常在系統(tǒng)碰到配置不當?shù)馁Y源(例如,拼寫錯誤的 JNDI 查找(JNDI lookup))時發(fā)生。在這種情況下,系統(tǒng)就將拋出一個受查異常。捕獲這些受查系統(tǒng)異常并將它們作為非受查異常(unchecked exception)拋出頗有意義。最重要的規(guī)則是,如果您對某個異常無能為力,那么它就是一個系統(tǒng)異常并且應當作為非受查異常拋出。

    受查異常是一個作為 java.lang.Exception 的子類的 Java 類。通過從 java.lang.Exception 派生子類,就強制您在編譯時捕獲這個異常。相反地, 非受查異常則是一個作為 java.lang.RuntimeException 的子類的 Java 類。從 java.lang.RuntimeException 派生子類確保了編譯器不會強制您捕獲這個異常。



    回頁首


    EJB 容器怎樣處理異常

    EJB 容器攔截 EJB 組件上的每一個方法調(diào)用。結(jié)果,方法調(diào)用中發(fā)生的每一個異常也被 EJB 容器攔截到。EJB 規(guī)范只處理兩種類型的異常:應用程序異常和系統(tǒng)異常。

    EJB 規(guī)范把 應用程序異常定義為在遠程接口中的方法說明上聲明的任何異常(而不是 RemoteException )。應用程序異常是業(yè)務工作流中的一種特殊情形。當這種類型的異常被拋出時,客戶機會得到一個恢復選項,這個選項通常是要求以一種不同的方式處理請求。不過,這并不意味著任何在遠程接口方法的 throws 子句中聲明的非受查異常都會被當作應用程序異常對待。EJB 規(guī)范明確指出,應用程序異常不應繼承 RuntimeException 或它的子類。

    當發(fā)生應用程序異常時,除非被顯式要求(通過調(diào)用關(guān)聯(lián)的 EJBContext 對象的 setRollbackOnly() 方法)回滾事務,否則 EJB 容器就不會這樣做。事實上,應用程序異常被保證以它原本的狀態(tài)傳送給客戶機:EJB 容器絕不會以任何方式包裝或修改異常。

    系統(tǒng)異常被定義為受查異常或非受查異常,EJB 方法不能從這種異常恢復。當 EJB 容器攔截到非受查異常時,它會回滾事務并執(zhí)行任何必要的清理工作。接著,它把該非受查異常包裝到 RemoteException 中,然后拋給客戶機。這樣,EJB 容器就把所有非受查異常作為 RemoteException (或者作為其子類,例如 TransactionRolledbackException )提供給客戶機。

    對于受查異常的情況,容器并不會自動執(zhí)行上面所描述的內(nèi)務處理。要使用 EJB 容器的內(nèi)部內(nèi)務處理,您將必須把受查異常作為非受查異常拋出。每當發(fā)生受查系統(tǒng)異常(如 NamingException )時,您都應該通過包裝原始的異常拋出 javax.ejb.EJBException 或其子類。因為 EJBException 本身是非受查異常,所以不需要在方法的 throws 子句中聲明它。EJB 容器捕獲 EJBException 或其子類,把它包裝到 RemoteException 中,然后把 RemoteException 拋給客戶機。

    雖然系統(tǒng)異常由應用程序服務器記錄(這是 EJB 規(guī)范規(guī)定的),但記錄格式將因應用程序服務器的不同而異。為了訪問所需的統(tǒng)計信息,企業(yè)常常需要對所生成的日志運行 shell/Perl 腳本。為了確保記錄格式的統(tǒng)一,在您的代碼中記錄異常會更好些。

    :EJB 1.0 規(guī)范要求把受查系統(tǒng)異常作為 RemoteException 拋出。從 EJB 1.1 規(guī)范起規(guī)定 EJB 實現(xiàn)類絕不應拋出 RemoteException



    回頁首


    常見的異常處理策略

    如果沒有異常處理策略,項目小組的不同開發(fā)者很可能會編寫以不同方式處理異常的代碼。由于同一個異常在系統(tǒng)的不同地方可能以不同的方式被描述和處理,所以,這至少會使產(chǎn)品支持小組感到迷惑。缺乏策略還會導致在整個系統(tǒng)的多個地方都有記錄。日志應該集中起來或者分成幾個可管理的單元。理想的情況是,應在盡可能少的地方記錄異常日志,同時不損失內(nèi)容。在這一部分及其后的幾個部分,我將展示可以在整個企業(yè)系統(tǒng)中以統(tǒng)一的方式實現(xiàn)的編碼策略。您可以從 參考資料部分下載本文開發(fā)的實用程序類。

    清單 1 顯示了來自會話 EJB 組件的一個方法。這個方法刪除某個客戶在特定日期前所下的全部訂單。首先,它獲取 OrderEJB 的 Home 接口。接著,它取回某個特定客戶的所有訂單。當它碰到在某個特定日期之前所下的訂單時,就刪除所訂購的商品,然后刪除訂單本身。請注意,拋出了三個異常,顯示了三種常見的異常處理做法。(為簡單起見,假設編譯器優(yōu)化未被使用。)


    清單 1. 三種常見的異常處理做法
    
    100  try {
    101    OrderHome homeObj = EJBHomeFactory.getInstance().getOrderHome();
    102    Collection orderCollection = homeObj.findByCustomerId(id);
    103    iterator orderItter = orderCollection.iterator();
    104    while (orderIter.hasNext()) {
    105      Order orderRemote = (OrderRemote) orderIter.getNext();
    106      OrderValue orderVal = orderRemote.getValue();
    107      if (orderVal.getDate() < "mm/dd/yyyy") {
    108        OrderItemHome itemHome = 
                  EJBHomeFactory.getInstance().getItemHome();
    109        Collection itemCol = itemHome.findByOrderId(orderId)
    110        Iterator itemIter = itemCol.iterator();
    111        while (itemIter.hasNext()) {
    112          OrderItem item = (OrderItem) itemIter.getNext();
    113          item.remove();
    114        }
    115        orderRemote.remove();
    116      }
    117    }
    118  } catch (NamingException ne) {
    119    throw new EJBException("Naming Exception occurred");
    120  } catch (FinderException fe) {
    121    fe.printStackTrace();
    122    throw new EJBException("Finder Exception occurred");
    123  } catch (RemoteException re) {
    124    re.printStackTrace();
    125    //Some code to log the message
    126    throw new EJBException(re);
    127  }
    

    現(xiàn)在,讓我們用上面所示的代碼來研究一下所展示的三種異常處理做法的缺點。

    拋出/重拋出帶有出錯消息的異常
    NamingException 可能發(fā)生在行 101 或行 108。當發(fā)生 NamingException 時,這個方法的調(diào)用者就得到 RemoteException 并向后跟蹤該異常到行 119。調(diào)用者并不能告知 NamingException 實際是發(fā)生在行 101 還是行 108。由于異常內(nèi)容要直到被記錄了才能得到保護,所以,這個問題的根源很難查出。在這種情形下,我們就說異常的內(nèi)容被“吞掉”了。正如這個示例所示,拋出或重拋出一個帶有消息的異常并不是一種好的異常處理解決辦法。

    記錄到控制臺并拋出一個異常
    FinderException 可能發(fā)生在行 102 或 109。不過,由于異常被記錄到控制臺,所以僅當控制臺可用時調(diào)用者才能向后跟蹤到行 102 或 109。這顯然不可行,所以異常只能被向后跟蹤到行 122。這里的推理同上。

    包裝原始的異常以保護其內(nèi)容
    RemoteException 可能發(fā)生在行 102、106、109、113 或 115。它在行 123 的 catch 塊被捕獲。接著,這個異常被包裝到 EJBException 中,所以,不論調(diào)用者在哪里記錄它,它都能保持完整。這種辦法比前面兩種辦法更好,同時演示了沒有日志策略的情況。如果 deleteOldOrders() 方法的調(diào)用者記錄該異常,那么將導致重復記錄。而且,盡管有了日志記錄,但當客戶報告某個問題時,產(chǎn)品日志或控制臺并不能被交叉引用。



    回頁首


    EJB 異常處理探試法

    EJB 組件應拋出哪些異常?您應將它們記錄到系統(tǒng)中的什么地方?這兩個問題盤根錯結(jié)、相互聯(lián)系,應該一起解決。解決辦法取決于以下因素:

    • 您的 EJB 系統(tǒng)設計:在良好的 EJB 設計中,客戶機絕不調(diào)用實體 EJB 組件上的方法。多數(shù)實體 EJB 方法調(diào)用發(fā)生在會話 EJB 組件中。如果您的設計遵循這些準則,則您應該用會話 EJB 組件來記錄異常。如果客戶機直接調(diào)用了實體 EJB 方法,則您還應該把消息記錄到實體 EJB 組件中。然而,存在一個難題:相同的實體 EJB 方法可能也會被會話 EJB 組件調(diào)用。在這種情形下,如何避免重復記錄呢?類似地,當一個會話 EJB 組件調(diào)用其它實體 EJB 方法時,您如何避免重復記錄呢?很快我們就將探討一種處理這兩種情況的通用解決方案。(請注意,EJB 1.1 并未從體系結(jié)構(gòu)上阻止客戶機調(diào)用實體 EJB 組件上的方法。在 EJB 2.0 中,您可以通過為實體 EJB 組件定義本地接口規(guī)定這種限制。)

    • 計劃的代碼重用范圍:這里的問題是您是打算把日志代碼添加到多個地方,還是打算重新設計、重新構(gòu)造代碼來減少日志代碼。

    • 您要為之服務的客戶機的類型:考慮您是將為 J2EE Web 層、單機 Java 應用程序、PDA 還是將為其它客戶機服務是很重要的。Web 層設計有各種形狀和大小。如果您在使用命令(Command)模式,在這個模式中,Web 層通過每次傳入一個不同的命令調(diào)用 EJB 層中的相同方法,那么,把異常記錄到命令在其中執(zhí)行的 EJB 組件中是很有用的。在多數(shù)其它的 Web 層設計中,把異常記錄到 Web 層本身要更容易,也更好,因為您需要把異常日志代碼添加到更少的地方。如果您的 Web 層和 EJB 層在同一地方并且不需要支持任何其它類型的客戶機,那么就應該考慮后一種選擇。

    • 您將處理的異常的類型(應用程序或系統(tǒng)):處理應用程序異常與處理系統(tǒng)異常有很大不同。系統(tǒng)異常的發(fā)生不受 EJB 開發(fā)者意圖的控制。因為系統(tǒng)異常的含義不清楚,所以內(nèi)容應指明異常的上下文。您已經(jīng)看到了,通過對原始異常進行包裝使這個問題得到了最好的處理。另一方面,應用程序異常是由 EJB 開發(fā)者顯式拋出的,通常包裝有一條消息。因為應用程序異常的含義清楚,所以沒有理由要保護它的上下文。這種類型的異常不必記錄到 EJB 層或客戶機層;它應該以一種有意義的方式提供給最終用戶,帶上指向所提供的解決方案的另一條備用途徑。系統(tǒng)異常消息沒必要對最終用戶很有意義。


    回頁首


    處理應用程序異常

    在這一部分及其后的幾個部分中,我們將更仔細地研究用 EJB 異常處理應用程序異常和系統(tǒng)異常,以及 Web 層設計。作為這個討論的一部分,我們將探討處理從會話和實體 EJB 組件拋出的異常的不同方式。

    實體 EJB 組件中的應用程序異常
    清單 2 顯示了實體 EJB 的一個 ejbCreate() 方法。這個方法的調(diào)用者傳入一個 OrderItemValue 并請求創(chuàng)建一個 OrderItem 實體。因為 OrderItemValue 沒有名稱,所以拋出了 CreateException


    清單 2. 實體 EJB 組件中的樣本 ejbCreate() 方法
    
    public Integer ejbCreate(OrderItemValue value) throws CreateException {
        if (value.getItemName() == null) {
          throw new CreateException("Cannot create Order without a name");
        }
        ..
        ..
        return null;
    }
    

    清單 2 顯示了 CreateException 的一個很典型的用法。類似地,如果方法的輸入?yún)?shù)的值不正確,則查找程序方法將拋出 FinderException

    然而,如果您在使用容器管理的持久性(CMP),則開發(fā)者無法控制查找程序方法,從而 FinderException 永遠不會被 CMP 實現(xiàn)拋出。盡管如此,在 Home 接口的查找程序方法的 throws 子句中聲明 FinderException 還是要更好一些。 RemoveException 是另一個應用程序異常,它在實體被刪除時被拋出。

    從實體 EJB 組件拋出的應用程序異常基本上限定為這三種類型( CreateExceptionFinderExceptionRemoveException )及它們的子類。多數(shù)應用程序異常都來源于會話 EJB 組件,因為那里是作出智能決策的地方。實體 EJB 組件一般是啞類,它們的唯一職責就是創(chuàng)建和取回數(shù)據(jù)。

    會話 EJB 組件中的應用程序異常
    清單 3 顯示了來自會話 EJB 組件的一個方法。這個方法的調(diào)用者設法訂購 n 件某特定類型的某商品。 SessionEJB() 方法計算出倉庫中的數(shù)量不夠,于是拋出 NotEnoughStockExceptionNotEnoughStockException 適用于特定于業(yè)務的場合;當拋出了這個異常時,調(diào)用者會得到采用另一個備用途徑的建議,讓他訂購更少數(shù)量的商品。


    清單 3. 會話 EJB 組件中的樣本容器回調(diào)方法
    
    public ItemValueObject[] placeOrder(int n, ItemType itemType) throws
    NotEnoughStockException {
    
        //Check Inventory.
        Collection orders = ItemHome.findByItemType(itemType);
        if (orders.size() < n) {
          throw NotEnoughStockException("Insufficient stock for " + itemType);
        }
    }
    



    回頁首


    處理系統(tǒng)異常

    系統(tǒng)異常處理是比應用程序異常處理更為復雜的論題。由于會話 EJB 組件和實體 EJB 組件處理系統(tǒng)異常的方式相似,所以,對于本部分的所有示例,我們都將著重于實體 EJB 組件,不過請記住,其中的大部分示例也適用于處理會話 EJB 組件。

    當引用其它 EJB 遠程接口時,實體 EJB 組件會碰到 RemoteException ,而查找其它 EJB 組件時,則會碰到 NamingException ,如果使用 bean 管理的持久性(BMP),則會碰到 SQLException 。與這些類似的受查系統(tǒng)異常應該被捕獲并作為 EJBException 或它的一個子類拋出。原始的異常應被包裝起來。清單 4 顯示了一種處理系統(tǒng)異常的辦法,這種辦法與處理系統(tǒng)異常的 EJB 容器的行為一致。通過包裝原始的異常并在實體 EJB 組件中將它重新拋出,您就確保了能夠在想記錄它的時候訪問該異常。


    清單 4. 處理系統(tǒng)異常的一種常見方式
    
    try {
        OrderHome orderHome = EJBHomeFactory.getInstance().getOrderHome();
        Order order = orderHome.findByPrimaryKey(Integer id);
    } catch (NamingException ne) {
        throw new EJBException(ne);
    } catch (SQLException se) {
        throw new EJBException(se);
    } catch (RemoteException re) {
        throw new EJBException(re);
    }
    

    避免重復記錄

    通常,異常記錄發(fā)生在會話 EJB 組件中。但如果直接從 EJB 層外部訪問實體 EJB 組件,又會怎么樣呢?要是這樣,您就不得不在實體 EJB 組件中記錄異常并拋出它。這里的問題是,調(diào)用者沒辦法知道異常是否已經(jīng)被記錄,因而很可能再次記錄它,從而導致重復記錄。更重要的是,調(diào)用者沒辦法訪問初始記錄時所生成的唯一的標識。任何沒有交叉引用機制的記錄都是毫無用處的。

    請考慮這種最糟糕的情形:單機 Java 應用程序訪問了實體 EJB 組件中的一個方法 foo() 。在一個名為 bar() 的會話 EJB 方法中也訪問了同一個方法。一個 Web 層客戶機調(diào)用會話 EJB 組件的方法 bar() 并也記錄了該異常。如果當從 Web 層調(diào)用會話 EJB 方法 bar() 時在實體 EJB 方法 foo() 中發(fā)生了一個異常,則該異常將被記錄到三個地方:先是在實體 EJB 組件,然后是在會話 EJB 組件,最后是在 Web 層。而且,沒有一個堆棧跟蹤可以被交叉引用!

    幸運的是,解決這些問題用常規(guī)辦法就可以很容易地做到。您所需要的只是一種機制,使調(diào)用者能夠:

    • 訪問唯一的標識
    • 查明異常是否已經(jīng)被記錄了

    您可以派生 EJBException 的子類來存儲這樣的信息。清單 5 顯示了 LoggableEJBException 子類:


    清單 5. LoggableEJBException ― EJBException 的一個子類
    
    public class LoggableEJBException extends EJBException {
        protected boolean isLogged;
        protected String uniqueID;
    
        public LoggableEJBException(Exception exc) {
    	super(exc);
    	isLogged = false;
    	uniqueID = ExceptionIDGenerator.getExceptionID();
        }
    
    	..
    	..
    }
    

    LoggableEJBException 有一個指示符標志( isLogged ),用于檢查異常是否已經(jīng)被記錄了。每當捕獲一個 LoggableEJBException 時,看一下該異常是否已經(jīng)被記錄了( isLogged == false )。如果 isLogged 為 false,則記錄該異常并把標志設置為 true

    ExceptionIDGenerator 類用當前時間和機器的主機名為異常生成唯一的標識。如果您喜歡,也可以用有想象力的算法來生成這個唯一的標識。如果您在實體 EJB 組件中記錄了異常,則這個異常將不會在別的地方被記錄。如果您沒有記錄就在實體 EJB 組件中拋出了 LoggableEJBException ,則這個異常將被記錄到會話 EJB 組件中,但不記錄到 Web 層中。

    單 6 顯示了使用這一技術(shù)重寫后的清單 4。您還可以繼承 LoggableException 以適合于您的需要(通過給異常指定錯誤代碼等)。


    清單 6. 使用 LoggableEJBException 的異常處理
    
    try {
        OrderHome orderHome = EJBHomeFactory.getInstance().getOrderHome();
        Order order = orderHome.findByPrimaryKey(Integer id);
    } catch (NamingException ne) {
        throw new LoggableEJBException(ne);
    } catch (SQLException se) {
        throw new LoggableEJBException(se);
    } catch (RemoteException re) {
        Throwable t = re.detail;
         if (t != null && t instanceof Exception) {
           throw new LoggableEJBException((Exception) re.detail);
         }  else {
           throw new LoggableEJBException(re);
         }
    }
    

    記錄 RemoteException

    從清單 6 中,您可以看到 naming 和 SQL 異常在被拋出前被包裝到了 LoggableEJBException 中。但 RemoteException 是以一種稍有不同 ― 而且要稍微花點氣力 ― 的方式處理的。
    會話 EJB 組件中的系統(tǒng)異常

    如果您決定記錄會話 EJB 異常,請使用 清單 7所示的記錄代碼;否則,請拋出異常,如 清單 6所示。您應該注意到,會話 EJB 組件處理異常可有一種與實體 EJB 組件不同的方式:因為大多數(shù) EJB 系統(tǒng)都只能從 Web 層訪問,而且會話 EJB 可以作為 EJB 層的虛包,所以,把會話 EJB 異常的記錄推遲到 Web 層實際上是有可能做到的。

    它之所以不同,是因為在 RemoteException 中,實際的異常將被存儲到一個稱為 detail (它是 Throwable 類型的)的公共屬性中。在大多數(shù)情況下,這個公共屬性保存有一個異常。如果您調(diào)用 RemoteExceptionprintStackTrace ,則除打印 detail 的堆棧跟蹤之外,它還會打印異常本身的堆棧跟蹤。您不需要像這樣的 RemoteException 的堆棧跟蹤。

    為了把您的應用程序代碼從錯綜復雜的代碼(例如 RemoteException 的代碼)中分離出來,這些行被重新構(gòu)造成一個稱為 ExceptionLogUtil 的類。有了這個類,您所要做的只是每當需要創(chuàng)建 LoggableEJBException 時調(diào)用 ExceptionLogUtil.createLoggableEJBException(e) 。請注意,在清單 6 中,實體 EJB 組件并沒有記錄異常;不過,即便您決定在實體 EJB 組件中記錄異常,這個解決方案仍然行得通。清單 7 顯示了實體 EJB 組件中的異常記錄:


    清單 7. 實體 EJB 組件中的異常記錄
    
    try {
        OrderHome orderHome = EJBHomeFactory.getInstance().getOrderHome();
        Order order = orderHome.findByPrimaryKey(Integer id);
    } catch (RemoteException re) {
        LoggableEJBException le = 
           ExceptionLogUtil.createLoggableEJBException(re);
        String traceStr = StackTraceUtil.getStackTrace(le);
        Category.getInstance(getClass().getName()).error(le.getUniqueID() +
    ":" + traceStr);
        le.setLogged(true);
        throw le;
    }
    

    您在清單 7 中看到的是一個非常簡單明了的異常記錄機制。一旦捕獲受查系統(tǒng)異常就創(chuàng)建一個新的 LoggableEJBException 。接著,使用類 StackTraceUtil 獲取 LoggableEJBException 的堆棧跟蹤,把它作為一個字符串。然后,使用 Log4J category 把該字符串作為一個錯誤加以記錄。

    StackTraceUtil 類的工作原理

    在清單 7 中,您看到了一個新的稱為 StackTraceUtil 的類。因為 Log4J 只能記錄 String 消息,所以這個類負責解決把堆棧跟蹤轉(zhuǎn)換成 String 的問題。清單 8 說明了 StackTraceUtil 類的工作原理:


    清單 8. StackTraceUtil 類
    
    
    public class StackTraceUtil {
    
    public static String getStackTrace(Exception e)
          {
              StringWriter sw = new StringWriter();
              PrintWriter pw = new PrintWriter(sw);
              return sw.toString();
          }
          ..
          ..
    }
    

    java.lang.Throwable 中缺省的 printStackTrace() 方法把出錯消息記錄到 System.errThrowable 還有一個重載的 printStackTrace() 方法,它把出錯消息記錄到 PrintWriterPrintStream 。上面的 StackTraceUtil 中的方法把 StringWriter 包裝到 PrintWriter 中。當 PrintWriter 包含有堆棧跟蹤時,它只是調(diào)用 StringWritertoString() ,以獲取該堆棧跟蹤的 String 表示。



    回頁首


    Web 層的 EJB 異常處理

    在 Web 層設計中,把異常記錄機制放到客戶機端往往更容易也更高效。要能做到這一點,Web 層就必須是 EJB 層的唯一客戶機。此外,Web 層必須建立在以下模式或框架之一的基礎(chǔ)上:

    • 模式:業(yè)務委派(Business Delegate)、FrontController 或攔截過濾器(Intercepting Filter)
    • 框架:Struts 或任何包含層次結(jié)構(gòu)的類似于 MVC 框架的框架

    為什么異常記錄應該在客戶機端上發(fā)生呢?嗯,首先,控制尚未傳到應用程序服務器之外。所謂的客戶機層在 J2EE 應用程序服務器本身上運行,它由 JSP 頁、servlet 或它們的助手類組成。其次,在設計良好的 Web 層中的類有一個層次結(jié)構(gòu)(例如:在業(yè)務委派(Business Delegate)類、攔截過濾器(Intercepting Filter)類、http 請求處理程序(http request handler)類和 JSP 基類(JSP base class)中,或者在 Struts Action 類中),或者 FrontController servlet 形式的單點調(diào)用。這些層次結(jié)構(gòu)的基類或者 Controller 類中的中央點可能包含有異常記錄代碼。對于基于會話 EJB 記錄的情況,EJB 組件中的每一個方法都必須具有記錄代碼。隨著業(yè)務邏輯的增加,會話 EJB 方法的數(shù)量也會增加,記錄代碼的數(shù)量也會增加。Web 層系統(tǒng)將需要更少的記錄代碼。如果您的 Web 層和 EJB 層在同一地方并且不需要支持任何其它類型的客戶機,那么您應該考慮這一備用方案。不管怎樣,記錄機制不會改變;您可以使用與前面的部分所描述的相同技術(shù)。



    回頁首


    真實世界的復雜性

    到現(xiàn)在為止,您已經(jīng)看到了簡單情形的會話和實體 EJB 組件的異常處理技術(shù)。然而,應用程序異常的某些組合可能會更令人費解,并且有多種解釋。清單 9 顯示了一個示例。 OrderEJBejbCreate() 方法試圖獲取 CustomerEJB 的一個遠程引用,這會導致 FinderExceptionOrderEJBCustomerEJB 都是實體 EJB 組件。您應該如何解釋 ejbCreate() 中的這個 FinderException 呢?是把它當作應用程序異常對待呢(因為 EJB 規(guī)范把它定義為標準應用程序異常),還是當作系統(tǒng)異常對待?


    清單 9. ejbCreate() 方法中的 FinderException
    
    public Object ejbCreate(OrderValue val) throws CreateException {
         try {
            if (value.getItemName() == null) {
              throw new CreateException("Cannot create Order without a name");
            }
            String custId = val.getCustomerId();
            Customer cust = customerHome.fingByPrimaryKey(custId);
            this.customer = cust;
         } catch (FinderException ne) {
         	  //How do you handle this Exception ?
         } catch (RemoteException re) {
    	  //This is clearly a System Exception
    	  throw ExceptionLogUtil.createLoggableEJBException(re);
         }
         return null;
    }
    

    雖然沒有什么東西阻止您把 FinderException 當應用程序異常對待,但把它當系統(tǒng)異常對待會更好。原因是:EJB 客戶機傾向于把 EJB 組件當黑箱對待。如果 createOrder() 方法的調(diào)用者獲得了一個 FinderException ,這對調(diào)用者并沒有任何意義。 OrderEJB 正試圖設置客戶遠程引用這件事對調(diào)用者來說是透明的。從客戶機的角度看,失敗僅僅意味著該訂單無法創(chuàng)建。

    這類情形的另一個示例是,會話 EJB 組件試圖創(chuàng)建另一個會話 EJB,因而導致了一個 CreateException 。一種類似的情形是,實體 EJB 方法試圖創(chuàng)建一個會話 EJB 組件,因而導致了一個 CreateException 。這兩個異常都應該當作系統(tǒng)異常對待。

    另一個可能碰到的挑戰(zhàn)是會話 EJB 組件在它的某個容器回調(diào)方法中獲得了一個 FinderException 。您必須逐例處理這類情況。您可能要決定是把 FinderException 當應用程序異常還是系統(tǒng)異常對待。請考慮清單 1 的情況,其中調(diào)用者調(diào)用了會話 EJB 組件的 deleteOldOrder 方法。如果我們不是捕獲 FinderException ,而是將它拋出,會怎么樣呢?在這一特定情況中,把 FinderException 當系統(tǒng)異常對待似乎是符合邏輯的。這里的理由是,會話 EJB 組件傾向于在它們的方法中做許多工作,因為它們處理工作流情形,并且它們對調(diào)用者而言是黑箱。

    另一方面,請考慮會話 EJB 正在處理下訂單的情形。要下一個訂單,用戶必須有一個簡檔 ― 但這個特定用戶卻還沒有。業(yè)務邏輯可能希望會話 EJB 顯式地通知用戶她的簡檔丟失了。丟失的簡檔很可能表現(xiàn)為會話 EJB 組件中的 javax.ejb.ObjectNotFoundExceptionFinderException 的一個子類)。在這種情況下,最好的辦法是在會話 EJB 組件中捕獲 ObjectNotFoundException 并拋出一個應用程序異常,讓用戶知道她的簡檔丟失了。

    即使是有了很好的異常處理策略,另一個問題還是經(jīng)常會在測試中出現(xiàn),而且在產(chǎn)品中也更加重要。編譯器和運行時優(yōu)化會改變一個類的整體結(jié)構(gòu),這會限制您使用堆棧跟蹤實用程序來跟蹤異常的能力。這就是您需要代碼重構(gòu)的幫助的地方。您應該把大的方法調(diào)用分割為更小的、更易于管理的塊。而且,只要有可能,異常類型需要多少就劃分為多少;每次您捕獲一個異常,都應該捕獲已規(guī)定好類型的異常,而不是捕獲所有類型的異常。



    回頁首


    結(jié)束語

    我們已經(jīng)在本文討論了很多東西,您可能想知道我們已經(jīng)討論的主要設計是否都物有所值。我的經(jīng)驗是,即便是在中小型項目中,在開發(fā)周期中,您的付出就已經(jīng)能看到回報,更不用說測試和產(chǎn)品周期了。此外,在宕機對業(yè)務具有毀滅性影響的生產(chǎn)系統(tǒng)中,良好的異常處理體系結(jié)構(gòu)的重要性再怎么強調(diào)也不過分。

    我希望本文所展示的最佳做法對您有益。要深入理解這里提供的某些信息,請參看 參考資料部分中的清單。



    回頁首


    參考資料

    • 您可以參閱本文在 developerWorks 全球站點上的 英文原文.

    • 單擊本文頂部或底部的 討論參加本文的 討論論壇


    • 下載本文所討論的 實用程序類


    • 您可以閱讀 Sun Microsystems 的 EJB 規(guī)范了解關(guān)于 EJB 體系結(jié)構(gòu)的更多信息。


    • Apache 的 Jakarta 項目有幾個珍品。 Log4J 框架即是其中之一。


    • Struts 框架是 Jakarta 項目的另一個珍品。Struts 建立在 MVC 體系結(jié)構(gòu)的基礎(chǔ)上,提供了一個徹底的分離,它把系統(tǒng)的表示層從系統(tǒng)的業(yè)務邏輯層中分離出來。


    • 要詳細了解 Struts,請閱讀 Malcom Davis 所寫的講述這個主題的很受歡迎的文章“ Struts, an open-source MVC implementation”( developerWorks,2001 年 2 月)。請注意:有一篇由 Wellie Chao 撰寫的最新文章定于 2002 年夏季發(fā)表。


    • 您可以通過閱讀相關(guān)的 J2SE 文檔了解關(guān)于新的 Java Logging API(java.util.logging)的更多信息。


    • 剛接觸 J2EE?來自“WebSphere 開發(fā)者園地”的這篇文章告訴您如何 用 WebSphere Studio Application Developer 開發(fā)和測試 J2EE 應用程序(2001 年 10 月)。


    • 如果您想更多了解關(guān)于測試基于 EJB 的系統(tǒng)的知識,請從最近的 developerWorks文章“ Test flexibly with AspectJ and mock objects”(2002 年 5 月)開始。


    • 如果您不滿足于單元測試,還想了解企業(yè)級系統(tǒng)測試的知識,請看看 IBM Performance Management, Testing, and Scalability Services企業(yè)級測試庫提供了什么。


    • Sun 的 J2EE 模式Web 站點著重于使用 J2EE 技術(shù)的模式、最佳做法、設計策略以及經(jīng)驗證的解決方案。


    • 您可以在 developerWorks Java 技術(shù)專區(qū)找到數(shù)以百計關(guān)于 Java 編程的方方面面的文章。


    回頁首


    關(guān)于作者

    Srikanth Shenoy 的照片

    Srikanth Shenoy 專門從事大型 J2EE 和 EAI 項目的體系結(jié)構(gòu)、設計、開發(fā)和部署工作。他在 Java 平臺一出現(xiàn)時就迷上了它,從此便全心投入。Srikanth 已經(jīng)幫他的制造業(yè)、物流業(yè)和金融業(yè)客戶實現(xiàn)了 Java 平臺“一次編寫,隨處運行”的夢想。您可以通過 srikanth@srikanth.org與他聯(lián)系。

    posted @ 2005-12-10 11:25 GHawk 閱讀(338) | 評論 (0)編輯 收藏

    正則表達式之道[轉(zhuǎn)]

    http://net.pku.edu.cn/~yhf/tao_regexps_zh.html#Regular%20Expressions%20Syntax

    原著:Steve Mansour
    sman@scruznet.com
    Revised: June 5, 1999
    (copied by jm /at/ jmason.org from http://www.scruz.net/%7esman/regexp.htm, after the original disappeared! )

    翻譯:Neo Lee
    什么是正則表達式

    一個正則表達式,就是用某種模式去匹配一類字符串的一個公式。很多人因為它們看上去比較古怪而且復雜所以不敢去使用——很不幸,這篇文章也不能夠改變這一點,不過,經(jīng)過一點點練習之后我就開始覺得這些復雜的表達式其實寫起來還是相當簡單的,而且,一旦你弄懂它們,你就能把數(shù)小時辛苦而且易錯的文本處理工作壓縮在幾分鐘(甚至幾秒鐘)內(nèi)完成。正則表達式被各種文本編輯軟件、類庫(例如Rogue Wave的tools.h++)、腳本工具(像awk/grep/sed)廣泛的支持,而且像Microsoft的Visual C++這種交互式IDE也開始支持它了。

    我們將在如下的章節(jié)中利用一些例子來解釋正則表達式的用法,絕大部分的例子是基于vi中的文本替換命令和grep文件搜索命令來書寫的,不過它們都是比較典型的例子,其中的概念可以在sed、awk、perl和其他支持正則表達式的編程語言中使用。你可以看看不同工具中的正則表達式這一節(jié),其中有一些在別的工具中使用正則表達式的例子。還有一個關(guān)于vi中文本替換命令(s)的簡單說明附在文后供參考。

    正則表達式基礎(chǔ)

    正則表達式由一些普通字符和一些元字符(metacharacters)組成。普通字符包括大小寫的字母和數(shù)字,而元字符則具有特殊的含義,我們下面會給予解釋。

    在最簡單的情況下,一個正則表達式看上去就是一個普通的查找串。例如,正則表達式"testing"中沒有包含任何元字符,,它可以匹配"testing"和"123testing"等字符串,但是不能匹配"Testing"。

    要想真正的用好正則表達式,正確的理解元字符是最重要的事情。下表列出了所有的元字符和對它們的一個簡短的描述。

    元字符   描述


    .
    匹配任何單個字符。例如正則表達式r.t匹配這些字符串:ratrutr t,但是不匹配root。 
    $
    匹配行結(jié)束符。例如正則表達式weasel$ 能夠匹配字符串"He's a weasel"的末尾,但是不能匹配字符串"They are a bunch of weasels."。 
    ^
    匹配一行的開始。例如正則表達式^When in能夠匹配字符串"When in the course of human events"的開始,但是不能匹配"What and When in the"。
    *
    匹配0或多個正好在它之前的那個字符。例如正則表達式.*意味著能夠匹配任意數(shù)量的任何字符。
    \
    這是引用府,用來將這里列出的這些元字符當作普通的字符來進行匹配。例如正則表達式\$被用來匹配美元符號,而不是行尾,類似的,正則表達式\.用來匹配點字符,而不是任何字符的通配符。
    [ ] 
    [c1-c2]
    [^c1-c2]
    匹配括號中的任何一個字符。例如正則表達式r[aou]t匹配ratrotrut,但是不匹配ret。可以在括號中使用連字符-來指定字符的區(qū)間,例如正則表達式[0-9]可以匹配任何數(shù)字字符;還可以制定多個區(qū)間,例如正則表達式[A-Za-z]可以匹配任何大小寫字母。另一個重要的用法是“排除”,要想匹配除了指定區(qū)間之外的字符——也就是所謂的補集——在左邊的括號和第一個字符之間使用^字符,例如正則表達式[^269A-Z] 將匹配除了2、6、9和所有大寫字母之外的任何字符。
    \< \>
    匹配詞(word)的開始(\<)和結(jié)束(\>)。例如正則表達式\<the能夠匹配字符串"for the wise"中的"the",但是不能匹配字符串"otherwise"中的"the"。注意:這個元字符不是所有的軟件都支持的。
    \( \)
    將 \( 和 \) 之間的表達式定義為“組”(group),并且將匹配這個表達式的字符保存到一個臨時區(qū)域(一個正則表達式中最多可以保存9個),它們可以用 \1\9 的符號來引用。
    |
    將兩個匹配條件進行邏輯“或”(Or)運算。例如正則表達式(him|her) 匹配"it belongs to him"和"it belongs to her",但是不能匹配"it belongs to them."。注意:這個元字符不是所有的軟件都支持的。
    +
    匹配1或多個正好在它之前的那個字符。例如正則表達式9+匹配9、99、999等。注意:這個元字符不是所有的軟件都支持的。
    ?
    匹配0或1個正好在它之前的那個字符。注意:這個元字符不是所有的軟件都支持的。
    \{i\}
    \{i,j\}
    匹配指定數(shù)目的字符,這些字符是在它之前的表達式定義的。例如正則表達式A[0-9]\{3\} 能夠匹配字符"A"后面跟著正好3個數(shù)字字符的串,例如A123、A348等,但是不匹配A1234。而正則表達式[0-9]\{4,6\} 匹配連續(xù)的任意4個、5個或者6個數(shù)字字符。注意:這個元字符不是所有的軟件都支持的。


    最簡單的元字符是點,它能夠匹配任何單個字符(注意包括新行符)。假定有個文件test.txt包含以下幾行內(nèi)容:

      he is a rat
      he is in a rut
      the food is Rotten
      I like root beer

    我們可以使用grep命令來測試我們的正則表達式,grep命令使用正則表達式去嘗試匹配指定文件的每一行,并將至少有一處匹配表達式的所有行顯示出來。命令

      grep r.t test.txt

    在test.txt文件中的每一行中搜索正則表達式r.t,并打印輸出匹配的行。正則表達式r.t匹配一個r接著任何一個字符再接著一個t。所以它將匹配文件中的ratrut,而不能匹配Rotten中的Rot,因為正則表達式是大小寫敏感的。要想同時匹配大寫和小寫字母,應該使用字符區(qū)間元字符(方括號)。正則表達式[Rr]能夠同時匹配Rr。所以,要想匹配一個大寫或者小寫的r接著任何一個字符再接著一個t就要使用這個表達式:[Rr].t

    要想匹配行首的字符要使用抑揚字符(^)——又是也被叫做插入符。例如,想找到text.txt中行首"he"打頭的行,你可能會先用簡單表達式he,但是這會匹配第三行的the,所以要使用正則表達式^he,它只匹配在行首出現(xiàn)的h

    有時候指定“除了×××都匹配”會比較容易達到目的,當抑揚字符(^)出現(xiàn)在方括號中是,它表示“排除”,例如要匹配he ,但是排除前面是t or s的情性(也就是theshe),可以使用:[^st]he

    可以使用方括號來指定多個字符區(qū)間。例如正則表達式[A-Za-z]匹配任何字母,包括大寫和小寫的;正則表達式[A-Za-z][A-Za-z]* 匹配一個字母后面接著0或者多個字母(大寫或者小寫)。當然我們也可以用元字符+做到同樣的事情,也就是:[A-Za-z]+ ,和[A-Za-z][A-Za-z]*完全等價。但是要注意元字符+ 并不是所有支持正則表達式的程序都支持的。關(guān)于這一點可以參考后面的正則表達式語法支持情況

    要指定特定數(shù)量的匹配,要使用大括號(注意必須使用反斜杠來轉(zhuǎn)義)。想匹配所有1001000的實例而排除1010000,可以使用:10\{2,3\},這個正則表達式匹配數(shù)字1后面跟著2或者3個0的模式。在這個元字符的使用中一個有用的變化是忽略第二個數(shù)字,例如正則表達式0\{3,\} 將匹配至少3個連續(xù)的0。

    簡單的例子

    這里有一些有代表性的、比較簡單的例子。

    vi 命令 作用


    :%s/ */ /g 把一個或者多個空格替換為一個空格。
    :%s/ *$// 去掉行尾的所有空格。
    :%s/^/ / 在每一行頭上加入一個空格。
    :%s/^[0-9][0-9]* // 去掉行首的所有數(shù)字字符。
    :%s/b[aeio]g/bug/g 將所有的bagbegbigbog改為bug。 
    :%s/t\([aou]\)g/h\1t/g 將所有tagtogtug分別改為hathothug(注意用group的用法和使用\1引用前面被匹配的字符)。

    中級的例子(神奇的咒語)

    例1

    將所有方法foo(a,b,c)的實例改為foo(b,a,c)。這里a、b和c可以是任何提供給方法foo()的參數(shù)。也就是說我們要實現(xiàn)這樣的轉(zhuǎn)換:

    之前   之后
    foo(10,7,2) foo(7,10,2)
    foo(x+13,y-2,10) foo(y-2,x+13,10)
    foo( bar(8), x+y+z, 5) foo( x+y+z, bar(8), 5)

    下面這條替換命令能夠?qū)崿F(xiàn)這一魔法:

      :%s/foo(\([^,]*\),\([^,]*\),\([^)]*\))/foo(\2,\1,\3)/g

    現(xiàn)在讓我們把它打散來加以分析。寫出這個表達式的基本思路是找出foo()和它的括號中的三個參數(shù)的位置。第一個參數(shù)是用這個表達式來識別的::\([^,]*\),我們可以從里向外來分析它: 

    [^,]   除了逗號之外的任何字符
    [^,]* 0或者多個非逗號字符
    \([^,]*\) 將這些非逗號字符標記為\1,這樣可以在之后的替換模式表達式中引用它
    \([^,]*\), 我們必須找到0或者多個非逗號字符后面跟著一個逗號,并且非逗號字符那部分要標記出來以備后用。

    現(xiàn)在正是指出一個使用正則表達式常見錯誤的最佳時機。為什么我們要使用[^,]*這樣的一個表達式,而不是更加簡單直接的寫法,例如:.*,來匹配第一個參數(shù)呢?設想我們使用模式.*來匹配字符串"10,7,2",它應該匹配"10,"還是"10,7,"?為了解決這個兩義性(ambiguity),正則表達式規(guī)定一律按照最長的串來,在上面的例子中就是"10,7,",顯然這樣就找出了兩個參數(shù)而不是我們期望的一個。所以,我們要使用[^,]*來強制取出第一個逗號之前的部分。

    這個表達式我們已經(jīng)分析到了:foo(\([^,]*\),這一段可以簡單的翻譯為“當你找到foo(就把其后直到第一個逗號之前的部分標記為\1”。然后我們使用同樣的辦法標記第二個參數(shù)為\2。對第三個參數(shù)的標記方法也是一樣,只是我們要搜索所有的字符直到右括號。我們并沒有必要去搜索第三個參數(shù),因為我們不需要調(diào)整它的位置,但是這樣的模式能夠保證我們只去替換那些有三個參數(shù)的foo()方法調(diào)用,在foo()是一個重載(overoading)方法時這種明確的模式往往是比較保險的。然后,在替換部分,我們找到foo()的對應實例,然后利用標記好的部分進行替換,是的第一和第二個參數(shù)交換位置。

    例2

    假設有一個CSV(comma separated value)文件,里面有一些我們需要的信息,但是格式卻有問題,目前數(shù)據(jù)的列順序是:姓名,公司名,州名縮寫,郵政編碼,現(xiàn)在我們希望講這些數(shù)據(jù)重新組織,以便在我們的某個軟件中使用,需要的格式為:姓名,州名縮寫-郵政編碼,公司名。也就是說,我們要調(diào)整列順序,還要合并兩個列來構(gòu)成一個新列。另外,我們的軟件不能接受逗號前后面有任何空格(包括空格和制表符)所以我們還必須要去掉逗號前后的所有空格。

    這里有幾行我們現(xiàn)在的數(shù)據(jù):

      Bill Jones,     HI-TEK Corporation ,  CA, 95011
      Sharon Lee Smith,  Design Works Incorporated,  CA, 95012
      B. Amos   ,  Hill Street Cafe,  CA, 95013
      Alexander Weatherworth,  The Crafts Store,  CA, 95014
      ...

    我們希望把它變成這個樣子:

      Bill Jones,CA 95011,HI-TEK Corporation
      Sharon Lee Smith,CA 95012,Design Works Incorporated
      B. Amos,CA 95013,Hill Street Cafe
      Alexander Weatherworth,CA 95014,The Crafts Store
      ...

    我們將用兩個正則表達式來解決這個問題。第一個移動列和合并列,第二個用來去掉空格。

    下面就是第一個替換命令:

      :%s/\([^,]*\),\([^,]*\),\([^,]*\),\(.*\)/\1,\3 \4,\2/

    這里的方法跟例1基本一樣,第一個列(姓名)用這個表達式來匹配:\([^,]*\),即第一個逗號之前的所有字符,而姓名內(nèi)容被用\1標記下來。公司名和州名縮寫字段用同樣的方法標記為\2\3,而最后一個字段用\(.*\)來匹配("匹配所有字符直到行末")。替換部分則引用上面標記的那些內(nèi)容來進行構(gòu)造。

    下面這個替換命令則用來去除空格:

      :%s/[ \t]*,[ \t]*/,/g

    我們還是分解來看:[ \t]匹配空格/制表符,[ \t]* 匹配0或多個空格/制表符,[ \t]*,匹配0或多個空格/制表符后面再加一個逗號,最后,[ \t]*,[ \t]*匹配0或多個空格/制表符接著一個逗號再接著0或多個空格/制表符。在替換部分,我們簡單的我們找到的所有東西替換成一個逗號。這里我們使用了結(jié)尾的可選的g參數(shù),這表示在每行中對所有匹配的串執(zhí)行替換(而不是缺省的只替換第一個匹配串)。

    例3

    假設有一個多字符的片斷重復出現(xiàn),例如:

    Billy tried really hard
    Sally tried really really hard
    Timmy tried really really really hard
    Johnny tried really really really really hard

    而你想把"really"、"really really",以及任意數(shù)量連續(xù)出現(xiàn)的"really"字符串換成一個簡單的"very"(simple is good!),那么以下命令:

    :%s/\(really \)\(really \)*/very /

    就會把上述的文本變成:

    Billy tried very hard
    Sally tried very hard
    Timmy tried very hard
    Johnny tried very hard

    表達式\(really \)*匹配0或多個連續(xù)的"really "(注意結(jié)尾有個空格),而\(really \)\(really \)* 匹配1個或多個連續(xù)的"really "實例。

    困難的例子(不可思議的象形文字)

    Coming soon.


    不同工具中的正則表達式

    OK,你已經(jīng)準備使用RE(regular expressions,正則表達式),但是你并準備使用vi。所以,在這里我們給出一些在其他工具中使用RE的例子。另外,我還會總結(jié)一下你在不同程序之間使用RE可能發(fā)現(xiàn)的區(qū)別。

    當然,你也可以在Visual C++編輯器中使用RE。選擇Edit->Replace,然后選擇"Regular expression"選擇框,F(xiàn)ind What輸入框?qū)厦娼榻B的vi命令:%s/pat1/pat2/g中的pat1部分,而Replace輸入框?qū)猵at2部分。但是,為了得到vi的執(zhí)行范圍和g選項,你要使用Replace All或者適當?shù)氖止ind Next and Replace(譯者按:知道為啥有人罵微軟弱智了吧,雖然VC中可以選中一個范圍的文本,然后在其中執(zhí)行替換,但是總之不夠vi那么靈活和典雅)。

    sed

    Sed是Stream EDitor的縮寫,是Unix下常用的基于文件和管道的編輯工具,可以在手冊中得到關(guān)于sed的詳細信息。

    這里是一些有趣的sed腳本,假定我們正在處理一個叫做price.txt的文件。注意這些編輯并不會改變源文件,sed只是處理源文件的每一行并把結(jié)果顯示在標準輸出中(當然很容易使用重定向來定制):

    sed腳本   描述


    sed 's/^$/d' price.txt 刪除所有空行
    sed 's/^[ \t]*$/d' price.txt 刪除所有只包含空格或者制表符的行
    sed 's/"http://g' price.txt 刪除所有引號

    awk

    awk是一種編程語言,可以用來對文本數(shù)據(jù)進行復雜的分析和處理。可以在手冊中得到關(guān)于awk的詳細信息。這個古怪的名字是它作者們的姓的縮寫(Aho,Weinberger和Kernighan)。

    在Aho,Weinberger和Kernighan的書The AWK Programming Language中有很多很好的awk的例子,請不要讓下面這些微不足道的腳本例子限制你對awk強大能力的理解。我們同樣假定我們針對price.txt文件進行處理,跟sed一樣,awk也只是把結(jié)果顯示在終端上。 

    awk腳本   描述


    awk '$0 !~ /^$/' price.txt 刪除所有空行
    awk 'NF > 0' price.txt awk中一個更好的刪除所有行的辦法
    awk '$2 ~ /^[JT]/ {print $3}' price.txt 打印所有第二個字段是'J'或者'T'打頭的行中的第三個字段
    awk '$2 !~ /[Mm]isc/ {print $3 + $4}' price.txt 針對所有第二個字段不包含'Misc'或者'misc'的行,打印第3和第4列的和(假定為數(shù)字)
    awk '$3 !~ /^[0-9]+\.[0-9]*$/ {print $0}' price.txt 打印所有第三個字段不是數(shù)字的行,這里數(shù)字是指d.d或者d這樣的形式,其中d是0到9的任何數(shù)字
    awk '$2 ~ /John|Fred/ {print $0}' price.txt 如果第二個字段包含'John'或者'Fred'則打印整行

    grep

    grep是一個用來在一個或者多個文件或者輸入流中使用RE進行查找的程序。它的name編程語言可以用來針對文件和管道進行處理。可以在手冊中得到關(guān)于grep的完整信息。這個同樣古怪的名字來源于vi的一個命令,g/re/p,意思是global regular expression print。

    下面的例子中我們假定在文件phone.txt中包含以下的文本,——其格式是姓加一個逗號,然后是名,然后是一個制表符,然后是電話號碼:

      Francis, John           5-3871
      Wong, Fred              4-4123
      Jones, Thomas           1-4122
      Salazar, Richard        5-2522

    grep命令   描述


    grep '\t5-...1' phone.txt 把所有電話號碼以5開頭以1結(jié)束的行打印出來,注意制表符是用\t表示的
    grep '^S[^ ]* R' phone.txt 打印所有姓以S打頭和名以R打頭的行
    grep '^[JW]' phone.txt 打印所有姓開頭是J或者W的行
    grep ', ....\t' phone.txt 打印所有姓是4個字符的行,注意制表符是用\t表示的
    grep -v '^[JW]' phone.txt 打印所有不以J或者W開頭的行
    grep '^[M-Z]' phone.txt 打印所有姓的開頭是M到Z之間任一字符的行
    grep '^[M-Z].*[12]' phone.txt 打印所有姓的開頭是M到Z之間任一字符,并且點號號碼結(jié)尾是1或者2的行

    egrep

    egrep是grep的一個擴展版本,它在它的正則表達式中支持更多的元字符。下面的例子中我們假定在文件phone.txt中包含以下的文本,——其格式是姓加一個逗號,然后是名,然后是一個制表符,然后是電話號碼:

      Francis, John           5-3871
      Wong, Fred              4-4123
      Jones, Thomas           1-4122
      Salazar, Richard        5-2522

    egrep command   Description


    egrep '(John|Fred)' phone.txt 打印所有包含名字John或者Fred的行
    egrep 'John|22$|^W' phone.txt 打印所有包含John 或者以22結(jié)束或者以W的行
    egrep 'net(work)?s' report.txt 從report.txt中找到所有包含networks或者nets的行


    正則表達式語法支持情況

    命令或環(huán)境 . [ ] ^ $ \( \) \{ \} ? + | ( )
    vi  X   X   X   X   X           
    Visual C++  X   X   X   X   X           
    awk  X   X   X   X       X   X   X   X 
    sed  X   X   X   X   X   X         
    Tcl  X   X   X   X   X     X   X   X   X 
    ex  X   X   X   X   X   X         
    grep  X   X   X   X   X   X         
    egrep  X   X  X   X   X     X   X   X   X 
    fgrep  X   X   X   X   X           
    perl  X  X  X  X  X    X  X  X  X

     


    vi替換命令簡介

    Vi的替換命令:

      :ranges/pat1/pat2/g

    其中

      : 這是Vi的命令執(zhí)行界面。
      range 是命令執(zhí)行范圍的指定,可以使用百分號(%)表示所有行,使用點(.)表示當前行,使用美元符號($)表示最后一行。你還可以使用行號,例如10,20表示第10到20行,.,$表示當前行到最后一行,.+2,$-5表示當前行后兩行直到全文的倒數(shù)第五行,等等。

      s 表示其后是一個替換命令。

      pat1 這是要查找的一個正則表達式,這篇文章中有一大堆例子。

      pat2 這是希望把匹配串變成的模式的正則表達式,這篇文章中有一大堆例子。

      g 可選標志,帶這個標志表示替換將針對行中每個匹配的串進行,否則則只替換行中第一個匹配串。

    網(wǎng)上有很多vi的在線手冊,你可以訪問他們以獲得更加完整的信息。

    posted @ 2005-12-06 22:10 GHawk 閱讀(258) | 評論 (0)編輯 收藏

    運用Jakarta Struts的七大實戰(zhàn)心法

    當作者 Chuck Cavaness(著有《Programming Jakarta Struts》一書)所在的網(wǎng)絡公司決定采用Struts框架之后,Chuck曾經(jīng)花費了好幾個月來研究如何用它來構(gòu)建公司的應用系統(tǒng)。本文敘述的正是作者在運用Struts過程中來之不易的若干經(jīng)驗和心得。如果你是個負責通過jsp和servlet開發(fā)Web應用的Java程序員,并且也正在考慮采用基于Struts的構(gòu)建方法的話,那么你會在這里發(fā)現(xiàn)很多頗有見地同時也很有價值的信息。

      1. 只在必要的時候才考慮擴展Struts框架

      一個好的framework有很多優(yōu)點,首先,它必須能夠滿足用戶的可預見的需求。為此 Struts為Web 應用提供了一個通用的架構(gòu),這樣開發(fā)人員可以把精力集中在如何解決實際業(yè)務問題上。其次,一個好的framework還必須能夠在適當?shù)牡胤教峁U展接口,以便應用程序能擴展該框架來更好的適應使用者的實際需要。

      如果Struts framework在任何場合,任何項目中都能很好的滿足需求,那真是太棒了。但是實際上,沒有一個框架聲稱能做到這一點。一定會有一些特定的應用需求是框架的開發(fā)者們無法預見到的。因此,最好的辦法就是提供足夠的擴展接口,使得開發(fā)工程師能夠調(diào)整struts來更好的符合他們的特殊要求。

      在Struts framework中有很多地方可供擴展和定制。幾乎所有的配置類都能被替換為某個用戶定制的版本,這只要簡單的修改一下Struts的配置文件就可以做到。

      其他組件如ActionServlet和 RequestProcessor 也能用自定義的版本代替. 甚至連Struts 1.1里才有的新特性也是按照擴展的原則來設計的。例如,在異常處理機制中就允許用戶定制異常處理的句柄,以便更好的對應用系統(tǒng)發(fā)生的錯誤做出響應。

      作為框架的這種可調(diào)整特性在它更適合你的應用的同時也在很大的程度上影響了項目開發(fā)的效果。首先,由于您的應用是基于一個現(xiàn)有的成熟的、穩(wěn)定的framework如Struts,測試過程中發(fā)現(xiàn)的錯誤數(shù)量將會大大減少,同時也能縮短開發(fā)時間和減少資源的投入。因為你不再需要投入開發(fā)力量用于編寫基礎(chǔ)框架的代碼了。

      然而, 實現(xiàn)更多的功能是要花費更大的代價的。我們必須小心避免不必要的濫用擴展性能, Struts是由核心包加上很多工具包構(gòu)成的,它們已經(jīng)提供了很多已經(jīng)實現(xiàn)的功能。因此不要盲目的擴展Struts框架,要先確定能不能采用其他方法使用現(xiàn)有的功能來實現(xiàn)。 在決定編寫擴展代碼前務必要確認Struts的確沒有實現(xiàn)你要的功能。否則重復的功能會導致混亂將來還得花費額外的精力清除它。

      2. 使用異常處理聲明

      要定義應用程序的邏輯流程,成熟的經(jīng)驗是推薦在代碼之外,用配置的方法來實現(xiàn),而不是寫死在程序代碼中的。在J2EE中,這樣的例子比比皆是。從實現(xiàn)EJB的安全性和事務性行為到描述JMS消息和目的地之間的關(guān)系,很多運行時的處理流程都是可以在程序之外定義的。

      Struts 創(chuàng)建者從一開始就采用這種方法,通過配置Struts的配置文件來定制應用系統(tǒng)運行時的各個方面。這一點在版本1.1的新特性上得到延續(xù),包括新的異常處理功能。在Struts framework以前的版本中,開發(fā)人員不得不自己處理Struts應用中發(fā)生的錯誤情況。在最新的版本中,情況大大的改觀了,Struts Framework提供了內(nèi)置的一個稱為 ExceptionHandler 的類, 用于系統(tǒng)缺省處理action類運行中產(chǎn)生的錯誤。這也是在上一個技巧中我們提到的framework許多可擴展接口之一。

      Struts缺省的 ExceptionHandler類會生成一個ActionError對象并保存在適當?shù)姆秶╯cope)對象中。這樣就允許JSP頁面使用錯誤類來提醒用戶出現(xiàn)什么問題。如果你認為這不能滿足你的需求,那么可以很方便的實現(xiàn)你自己的ExcepionHandler類。

      具體定制異常處理的方法和機制

      要定制自己的異常處理機制,第一步是繼承org.apache.struts.action.ExceptionHandler類。這個類有2個方法可以覆蓋,一個是excute()另外一個是storeException(). 在多數(shù)情況下,只需要覆蓋其中的excute()方法。下面是ExceptionHandler類的excute()方法聲明:
      正如你看到的,該方法有好幾個參數(shù),其中包括原始的異常。方法返回一個ActionForward對象,用于異常處理結(jié)束后將controller類帶到請求必須轉(zhuǎn)發(fā)的地方去。

      當然您可以實現(xiàn)任何處理,但一般而言,我們必須檢查拋出的異常,并針對該類型的異常進行特定的處理。缺省的,系統(tǒng)的異常處理功能是創(chuàng)建一個出錯信息,同時把請求轉(zhuǎn)發(fā)到配置文件中指定的地方去。 定制異常處理的一個常見的例子是處理嵌套異常。假設該異常包含有嵌套異常,這些嵌套異常又包含了其他異常,因此我們必須覆蓋原來的execute()方法,對每個異常編寫出錯信息。

      一旦你創(chuàng)建了自己的ExceptionHandler 類,就應該在Struts配置文件中的部分聲明這個類,以便讓Struts知道改用你自定義的異常處理取代缺省的異常處理.

      可以配置你自己的ExceptionHandler 類是用于Action Mapping特定的部分還是所有的Action對象。如果是用于Action Mapping特定的部分就在元素中配置。如果想讓這個類可用于所有的Action對象,可以在 元素中指定。例如,假設我們創(chuàng)建了異常處理類CustomizedExceptionHandler用于所有的Action類, 元素定義如下所示:

      在元素中可以對很多屬性進行設置。在本文中,最重要的屬性莫過于handler屬性, handler屬性的值就是自定義的繼承了ExceptionHandler類的子類的全名。 假如該屬性沒有定義,Struts會采用自己的缺省值。當然,其他的屬性也很重要,但如果想覆蓋缺省的異常處理的話,handler無疑是最重要的屬性。

      最后必須指出的一點是,你可以有不同的異常處理類來處理不同的異常。在上面的例子中,CustomizedExceptionHandler用來處理任何java.lang.Exception的子類. 其實,你也可以定義多個異常處理類,每一個專門處理不同的異常樹。下面的XML片斷解釋了如何配置以實現(xiàn)這一點。

      在這里,一旦有異常拋出,struts framework將試圖在配置文件中找到ExceptionHandler,如果沒有找到,那么struts將沿著該異常的父類鏈一層層往上找直到發(fā)現(xiàn)匹配的為止。因此,我們可以定義一個層次型的異常處理關(guān)系結(jié)構(gòu),在配置文件中已經(jīng)體現(xiàn)了這一點。

      3. 使用應用模塊(Application Modules)

      Struts 1.1的一個新特性是應用模塊的概念。應用模塊允許將單個Struts應用劃分成幾個模塊,每個模塊有自己的Struts配置文件,JSP頁面,Action等等。這個新特性是為了解決大中型的開發(fā)隊伍抱怨最多的一個問題,即為了更好的支持并行開發(fā)允許多個配置文件而不是單個配置文件。

      注:在早期的beta版本中,該特性被稱為子應用(sub-applications),最近的改名目的是為了更多地反映它們在邏輯上的分工。

      顯然,當很多開發(fā)人員一起參加一個項目時,單個的Struts配置文件很容易引起資源沖突。應用模塊允許Struts按照功能要求進行劃分,許多情況已經(jīng)證明這樣更貼近實際。例如,假設我們要開發(fā)一個典型的商店應用程序。可以將組成部分劃分成模塊比如catalog(商品目錄), customer(顧客), customer service(顧客服務), order(訂單)等。每個模塊可以分布到不同的目錄下,這樣各部分的資源很容易定位,有助于開發(fā)和部署。圖1 顯示了該應用的目錄結(jié)構(gòu)。

      圖 1. 一個典型的商店應用程序的目錄結(jié)構(gòu)
      

      注:如果你無需將項目劃分成多個模塊,Struts框架支持一個缺省的應用模塊。這就使得應用程序也可以在1.0版本下創(chuàng)建,具有可移植性,因為應用程序會自動作為缺省的應用模塊。

      為了使用多應用模塊功能,必須執(zhí)行以下幾個準備步驟:

      ? 為每個應用模塊創(chuàng)建獨立的Struts配置文件。

      ? 配置Web 部署描述符 Web.xml文件。

      ? 使用org.apache.struts.actions.SwitchAction 來實現(xiàn)程序在模塊之間的跳轉(zhuǎn).

      創(chuàng)建獨立的Struts配置文件

      每個Struts應用模塊必須擁有自己的配置文件。允許創(chuàng)建自己的獨立于其他模塊的Action,ActionForm,異常處理甚至更多。

      繼續(xù)以上面的商店應用程序為例,我們可以創(chuàng)建以下的配置文件:一個文件名為struts-config-catalog.xml,包含catalog(商品目錄)、items(商品清單)、和其它與庫存相關(guān)的功能的配置信息;另一個文件名為struts- config-order.xml, 包含對order(訂單)和order tracking(訂單跟蹤)的設置。第三個配置文件是struts-config.xml,其中含有屬于缺省的應用模塊中的一般性的功能。

      配置Web部署描述符

      在Struts的早期版本中,我們在Web.xml中指定Struts配置文件的路徑。好在這點沒變,有助于向后兼容。但對于多個應用模塊,我們需要在Web部署描述符中增加新的配置文件的設定。

      對于缺省的應用(包括Struts的早期版本),Struts framework 在Web.xml文件中查找?guī)в衏onfig的元素,用于載入Action mapping 和其它的應用程序設定。作為例子,以下的XML片斷展現(xiàn)一個典型的元素:

      注:如果在現(xiàn)有的元素中找不到"config"關(guān)鍵字,Struts framework將缺省地使用/WEB/struts-config.xml

      為了支持多個應用模塊(Struts 1.1的新特性),必須增加附加的元素。與缺省的元素不同的是,附加的元素與每個應用模塊對應,必須以config/xxx的形式命名,其中字符串xxx代表該模塊唯一的名字。例如,在商店應用程序的例子中,元素可定義如下(注意粗體字部分):

      第一個 元素對應缺省的應用模塊。第二和第三個元素分別代表非缺省應用模塊catalog 和 order。

      當Struts載入應用程序時,它首先載入缺省應用模塊的配置文件。然后查找?guī)в凶址甤onfig/xxx 形式的附加的初始化參數(shù)。對每個附加的配置文件也進行解析并載入內(nèi)存。這一步完成后,用戶就可以很隨意地用config/后面的字符串也就是名字來調(diào)用相應的應用模塊。

      多個應用模塊之間調(diào)用Action類

      在為每個應用模塊創(chuàng)建獨立的配置文件之后,我們就有可能需要調(diào)用不同的模塊中Action。為此必須使用Struts框架提供的SwitchAction類。Struts 會自動將應用模塊的名字添加到URL,就如Struts 自動添加應用程序的名字加到URL一樣。應用模塊是對框架的一個新的擴充,有助于進行并行的團隊開發(fā)。如果你的團隊很小那就沒必要用到這個特性,不必進行模塊化。當然,就算是只有一個模塊,系統(tǒng)還是一樣的運作。

      4. 把JSP放到WEB-INF后以保護JSP源代碼

      為了更好地保護你的JSP避免未經(jīng)授權(quán)的訪問和窺視, 一個好辦法是將頁面文件存放在Web應用的WEB-INF目錄下。

      通常JSP開發(fā)人員會把他們的頁面文件存放在Web應用相應的子目錄下。一個典型的商店應用程序的目錄結(jié)構(gòu)如圖2所示。跟catalog (商品目錄)相關(guān)的JSP被保存在catalog子目錄下。跟customer相關(guān)的JSP,跟訂單相關(guān)的JSP等都按照這種方法存放。
      
      這種方法的問題是這些頁面文件容易被偷看到源代碼,或被直接調(diào)用。某些場合下這可能不是個大問題,可是在特定情形中卻可能構(gòu)成安全隱患。用戶可以繞過Struts的controller直接調(diào)用JSP同樣也是個問題。

      為了減少風險,可以把這些頁面文件移到WEB-INF 目錄下。基于Servlet的聲明,WEB-INF不作為Web應用的公共文檔樹的一部分。因此,WEB-INF 目錄下的資源不是為客戶直接服務的。我們?nèi)匀豢梢允褂肳EB-INF目錄下的JSP頁面來提供視圖給客戶,客戶卻不能直接請求訪問JSP。

      采用前面的例子,圖3顯示將JSP頁面移到WEB-INF 目錄下后的目錄結(jié)構(gòu)

      如果把這些JSP頁面文件移到WEB-INF 目錄下,在調(diào)用頁面的時候就必須把"WEB-INF"添加到URL中。例如,在一個Struts配置文件中為一個logoff action寫一個Action mapping。其中JSP的路徑必須以"WEB-INF"開頭。如下所示:請注意粗體部分.

      這個方法在任何情況下都不失為Struts實踐中的一個好方法。是唯一要注意的技巧是你必須把JSP和一個Struts action聯(lián)系起來。即使該Action只是一個很基本的很簡單JSP,也總是要調(diào)用一個Action,再由它調(diào)用JSP。

      最后要說明的是,并不是所有的容器都能支持這個特性。WebLogic早期的版本不能解釋Servlet聲明,因此無法提供支持,據(jù)報道在新版本中已經(jīng)改進了。總之使用之前先檢查一下你的Servlet容器。

      5. 使用 Prebuilt Action類提升開發(fā)效率

      Struts framework帶有好幾個prebuilt Action類,使用它們可以大大節(jié)省開發(fā)時間。其中最有用的是org.apache.struts.actions.ForwardAction 和 org.apache.struts.actions.DispatchAction.

      使用 ForwardAction

      在應用程序中,可能會經(jīng)常出現(xiàn)只要將Action對象轉(zhuǎn)發(fā)到某個JSP的情況。在上一點中曾提到總是由Action調(diào)用JSP是個好習慣。如果我們不必在Action中執(zhí)行任何業(yè)務邏輯,卻又想遵循從Action訪問頁面的話,就可以使用ForwardAction,它可以使你免去創(chuàng)建許多空的Action類。運用ForwardAction的好處是不必創(chuàng)建自己的Action類,你需要做的僅僅是在Struts配置文件中配置一個Action mapping。

      舉個例子,假定你有一個JSP文件index.jsp ,而且不能直接調(diào)用該頁面,必須讓程序通過一個Action類調(diào)用,那么,你可以建立以下的Action mapping來實現(xiàn)這一點:

      正如你看到的,當 /home 被調(diào)用時, 就會調(diào)用ForwardAction 并把請求轉(zhuǎn)發(fā)到 index.jsp 頁面.

      再討論一下不通過一個Action類直接轉(zhuǎn)發(fā)到某個頁面的情況,必須注意我們?nèi)匀皇褂?ACTION>元素中的forward屬性來實現(xiàn)轉(zhuǎn)發(fā)的目標。這時元素定義如下:

      以上兩種方法都可以節(jié)省你的時間,并有助于減少一個應用所需的文件數(shù)。

      使用 DispatchAction

      DispatchAction是Struts包含的另一個能大量節(jié)省開發(fā)時間的Action類。與其它Action類僅提供單個execute()方法實現(xiàn)單個業(yè)務不同,DispatchAction允許你在單個Action類中編寫多個與業(yè)務相關(guān)的方法。這樣可以減少Action類的數(shù)量,并且把相關(guān)的業(yè)務方法集合在一起使得維護起來更容易。

      要使用DispatchAction的功能,需要自己創(chuàng)建一個類,通過繼承抽象的DispatchAction得到。對每個要提供的業(yè)務方法必須有特定的方法signature。例如,我們想要提供一個方法來實現(xiàn)對購物車添加商品清單,創(chuàng)建了一個類ShoppingCartDispatchAction提供以下的方法:

      那么,這個類很可能還需要一個deleteItem()方法從客戶的購物車中刪除商品清單,還有clearCart()方法清除購物車等等。這時我們就可以把這些方法集合在單個Action類,不用為每個方法都提供一個Action類。

      在調(diào)用ShoppingCartDispatchAction里的某個方法時,只需在URL中提供方法名作為參數(shù)值。就是說,調(diào)用addItem()方法的 URL看起來可能類似于:

      http://myhost/storefront/action/cart?method=addItem

      其中method參數(shù)指定ShoppingCartDispatchAction中要調(diào)用的方法。參數(shù)的名稱可以任意配置,這里使用的"method"只是一個例子。參數(shù)的名稱可以在Struts配置文件中自行設定。

      6.使用動態(tài)ActionForm

      在Struts framework中,ActionForm對象用來包裝HTML表格數(shù)據(jù)(包括請求),并返回返回動態(tài)顯示給用戶的數(shù)據(jù)。它們必須是完全的JavaBean,并繼承.Struts 里面的ActionForm類,同時,用戶可以有選擇地覆蓋兩個缺省方法。

      該特性能節(jié)省很多時間,因為它可以協(xié)助進行自動的表現(xiàn)層的驗證。ActionForm的唯一缺點是必須為不同的HTML表格生成多個ActionForm 類以保存數(shù)據(jù)。例如,如果有一個頁面含有用戶的注冊信息,另一個頁面則含有用戶的介紹人的信息,那么就需要有兩個不同的ActionForm類。這在大的應用系統(tǒng)中就會導致過多的ActionForm類。Struts 1.1對此做出了很好的改進,引入了動態(tài)ActionForm類概念

      通過Struts framework中的DynaActionForm類及其子類可以實現(xiàn)動態(tài)的ActionForm ,動態(tài)的ActionForm允許你通過Struts的配置文件完成ActionForm的全部配置;再也沒有必要在應用程序中創(chuàng)建具體的ActionForm類。具體配置方法是:在Struts的配置文件通過增加一個元素,將type屬性設定成DynaActionForm或它的某個子類的全名。下面的例子創(chuàng)建了一個動態(tài)的ActionForm名為logonForm,它包含兩個實例變量:username 和 password.

      動態(tài)的ActionForm可以用于Action類和JSP,使用方法跟普通的ActionForm相同,只有一個小差別。如果使用普通的ActionForm對象則需要提供get 和 set方法取得和設置數(shù)據(jù)。以上面的例子而言,我們需要提供getUsername() 和 setUsername()方法取得和設置username變量,同樣地有一對方法用于取得和設置password變量.

      這里我們使用的是DynaActionForm,它將變量保存在一個Map類對象中,所以必須使用DynaActionForm 類中的get(name) 和 set(name)方法,其中參數(shù)name是要訪問的實例變量名。例如要訪問DynaActionForm中username的值,可以采用類似的代碼:

      String username = (String)form.get("username");

      由于值存放在一個Map對象,所以要記得對get()方法返回的Object對象做強制性類型轉(zhuǎn)換。

      DynaActionForm有好幾個很有用的子類。其中最重要的是DynaValidatorForm ,這個動態(tài)的ActionForm和Validator 一起利用公共的Validator包來提供自動驗證。這個特性使你得以在程序代碼之外指定驗證規(guī)則。將兩個特性結(jié)合使用對開發(fā)人員來說將非常有吸引力。

      7. 使用可視化工具

      自從Struts 1.0 分布以來,就出現(xiàn)了不少可視化工具用于協(xié)助創(chuàng)建,修改和維護Struts的配置文件。配置文件本身是基于XML格式,在大中型的開發(fā)應用中會增大變得很笨拙。為了更方便的管理這些文件,一旦文件大到你無法一目了然的時候,建議試著采用其中的一種GUI 工具協(xié)助開發(fā)。商業(yè)性的和開放源代碼的工具都有不少,表1列出了可用的工具和其相關(guān)鏈接,從那里可以獲取更多信息。

      表 1. Struts GUI 工具
      應用程序 性質(zhì) 網(wǎng)址
      Adalon 商業(yè)軟件 http://www.synthis.com/products/adalon
      Easy Struts 開放源碼 http://easystruts.sourceforge.net/
      Struts Console 免費 http://www.jamesholmes.com/struts/console
      JForms 商業(yè)軟件 http://www.solanasoft.com/
      Camino 商業(yè)軟件 http://www.scioworks.com/scioworks_camino.html
      Struts Builder 開放源碼 http://sourceforge.net/projects/rivernorth/
      StrutsGUI 免費 http://www.alien-factory.co.uk/struts/struts-index.html

      相關(guān)資源

      要獲取更為全面的Struts GUI 工具列表 (包括免費的和商業(yè)性的), 請訪問 Struts resource page.

    posted @ 2005-12-02 13:37 GHawk 閱讀(426) | 評論 (1)編輯 收藏

    我應該使用哪種樣式的 WSDL 呢? (From IBM developerWorks)

    我應該使用哪種樣式的 WSDL 呢?
    內(nèi)容:
    引言
    RPC/編碼
    RPC/文字
    文檔/編碼
    文檔/文字
    文檔/文字包裝模式
    為什么不始終采用文檔/文字包裝的樣式
    SOAP 響應消息
    結(jié)束語
    參考資料
    關(guān)于作者
    對本文的評價
    相關(guān)內(nèi)容:
    Web services with WSDL
    Handle namespaces in SOAP messages you create by hand
    訂閱:
    developerWorks 時事通訊

    級別: 高級

    Russell Butek
    Web 服務顧問, IBM
    2003 年 10 月 31 日
    2005 年 6 月 29 日 更新

    WSDL 綁定樣式可以是 RPC 樣式或文檔樣式。用法可以是編碼的,也可以是文字的。您如何決定使用哪一種樣式/用法的組合呢?本文將幫助您解決這個問題。

    引言
    Web 服務是通過 WSDL 文檔來描述的。WSDL 綁定描述了如何把服務綁定到消息傳遞協(xié)議(特別是 SOAP 消息傳遞協(xié)議)。WSDL SOAP 綁定可以是 RPC 樣式的綁定,也可以是文檔樣式的綁定。同樣,SOAP 綁定可以有編碼的用法,也可以有文字的用法。這給我們提供了四種樣式/用法模型:

    1. RPC/編碼
    2. RPC/文字
    3. 文檔/編碼
    4. 文檔/文字

    除了這些樣式之外,還有一種樣式也很常見,它稱為文檔/文字包裝的樣式,算上這一種,在創(chuàng)建 WSDL 文件時您就有了五種綁定樣式可以從中選擇。您應該選擇哪一種呢?

    在我進一步討論以前,讓我闡明一些容易混淆的地方。這里,這些術(shù)語是非常不合適的:RPC 與文檔。這些術(shù)語意味著 RPC 樣式應該用于 RPC 編程模型,文檔樣式應該用于文檔或消息編程模型。 但事實完全不是這樣。樣式對于編程模型沒有任何意義。它只是指明了如何將 WSDL 綁定轉(zhuǎn)化為 SOAP 消息。其他就沒什么了。你可以將任一種樣式用于任何編程模型。

    同樣,術(shù)語編碼文字只對于 WSDL 到 SOAP 映射有意義,可是,至少這里,這兩個單詞的字面意思更容易理解一些。

    對于這篇討論,讓我們從清單 1 中的 Java 方法開始,并且應用 JAX-RPC Java-to-WSDL 規(guī)則(參閱參考資料查看 JAX-RPC 1.1 規(guī)范)。

    清單 1. Java 方法
    public void myMethod(int x, float y);

    RPC/編碼
    采用清單 1 中的方法并且使用你喜歡的 Java-to-WSDL 工具來運行,指定您想讓它生成 RPC/編碼的 WSDL。您最后應該得到如清單 2 所示的 WSDL 片斷。

    清單 2. 用于 myMethod 的 RPC/編碼的 WSDL
    <message name="myMethodRequest">
        <part name="x" type="xsd:int"/>
        <part name="y" type="xsd:float"/>
    </message>
    <message name="empty"/>
    
    <portType name="PT">
        <operation name="myMethod">
            <input message="myMethodRequest"/>
            <output message="empty"/>
        </operation>
    </portType>
    
    <binding .../>  
    <!-- I won't bother with the details, just assume it's RPC/encoded. -->

    現(xiàn)在用“5”作為參數(shù) x 的值,“5.0”作為參數(shù) y 的值來調(diào)用這個方法。發(fā)送一個如清單 3 所示的SOAP 消息。

    清單 3. 用于 myMethod 的 RPC/編碼的 SOAP 消息
    <soap:envelope>
        <soap:body>
            <myMethod>
                <x xsi:type="xsd:int">5</x>
                <y xsi:type="xsd:float">5.0</y>
            </myMethod>
        </soap:body>
    </soap:envelope>

    關(guān)于前綴和命名空間的注意事項
    為了簡單起見,在本文的大部分 XML 示例中,我省略了命名空間和前綴。不過,我還是使用了少數(shù)前綴,您可以假定它們是用下列名稱空間進行定義的:

    • xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    • xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    • xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"

    關(guān)于命名空間和 WSDL-to-SOAP 映射的討論,請參考文章“Handle namespaces in SOAP messages you create by hand”(參閱 參考資料)。

    關(guān)于 RPC/編碼例子中的 WSDL 和 SOAP 消息有一些需要注意的地方:

    優(yōu)點

    • WSDL 盡可能的簡單明了。
    • 操作名出現(xiàn)在消息中,因此接收者可以很容易的將消息分派到操作的實現(xiàn)。



    缺點

    遵照 WS-I
    各種 Web 服務規(guī)范有時候是不一致和不明確的。WS-I 組織成立用來解決這些規(guī)范上的問題。它已經(jīng)定義了許多概要,指明了你應該如何編寫 Web 服務來實現(xiàn)互操作性。要獲取 WS-I 的更多信息,請參閱參考資料中的 WS-I 鏈接。

    • 類型編碼信息(xsi:type="xsd:int")通常就是降低吞吐量性能的開銷。
    • 你不能很容易的驗證這個消息的有效性,因為只有 <x ...>5</x><y ...>5.0</y> 行包含 Schema 中定義的內(nèi)容;soap:body 內(nèi)容的其余部分來自于 WSDL 定義。
    • 雖然它是合法的 WSDL,但 RPC/encoded 是不遵守 WS-I 的。

    有沒有一種方法可以取其精華,棄其糟粕呢?可能有。讓我們看一下 RPC/文字樣式。

    RPC/文字
    用于這個方法的 RPC/文字樣式的 WSDL 看起來與 RPC/編碼的 WSDL(清單 4)幾乎一樣。綁定的用法從 編碼 變?yōu)?文字。僅此而已。

    清單 4. 用于 myMethod 的 RPC/文字樣式的 WSDL
    <message name="myMethodRequest">
        <part name="x" type="xsd:int"/>
        <part name="y" type="xsd:float"/%gt;
    </message>
    <message name="empty"/>
    
    <portType name="PT">
        <operation name="myMethod">
            <input message="myMethodRequest"/>
            <output message="empty"/>
        </operation>
    </portType>
    
    <binding .../>  
    <!-- I won't bother with the details, just assume it's RPC/literal. -->

    RPC/文字的 SOAP 消息又是怎樣的呢(參閱清單 5)?這里的更改要多一點。去掉了類型編碼。

    清單 5. 用于 myMethod 的 RPC/literal SOAP 消息
    <soap:envelope>
        <soap:body>
            <myMethod>
                <x>5</x>
                <y>5.0</y>
            </myMethod>
        </soap:body>
    </soap:envelope>

    關(guān)于 xsi:type 和文字用法的注意事項
    雖然在一般情況下,xsi:type 沒有出現(xiàn)在文字 WSDL 的 SOAP 消息中,但是仍然有一些情況,類型信息是必須的,并且它將以多種形式出現(xiàn)。如果 API 期望一個基礎(chǔ)類型,并且發(fā)送一個擴展實例,則必須提供這個實例的類型以便正確的反序列化該對象。

    這里是這種方法的優(yōu)點和缺點:

    優(yōu)點

    • WSDL 盡可能的簡單明了。
    • 操作名仍然出現(xiàn)在消息中。
    • 去掉了類型編碼。
    • RPC/文字是遵循 WS-I 的。

    缺點

    • 你仍然不能很容易的驗證這個消息的有效性,因為只有 <x ...>5</x><y ...>5.0</y> 行中包含定義在 Schema 中的內(nèi)容;soap:body 內(nèi)容的其余部分來自于 WSDL 定義。

    文檔樣式如何呢?它們能夠幫助克服這些困難嗎?

    文檔/編碼
    沒有人使用這個樣式。它不遵循 WS-I。因此此處略過。

    文檔/文字
    文檔/文字的 WSDL 在 RPC/文字的 WSDL 基礎(chǔ)上做了一些修改。其不同點已經(jīng)在清單 6 中指出。

    清單 6. 用于 myMethod 的文檔/文字 WSDL
    <types>
        <schema>
            <element name="xElement" type="xsd:int"/>
            <element name="yElement" type="xsd:float"/>
        </schema>
    </types>
    
    <message name="myMethodRequest">
        <part name="x" element="xElement"/>
        <part name="y" element="yElement"/>
    </message>
    <message name="empty"/>
    
    <portType name="PT">
        <operation name="myMethod">
            <input message="myMethodRequest"/>
            <output message="empty"/>
        </operation>
    </portType>
    
    <binding .../>  
    <!-- I won't bother with the details, just assume it's document/literal. -->

    用于這個 WSDL 的 SOAP 消息如清單 7 所示:

    清單 7. 用于 myMethod 的文檔/文字 SOAP 消息
    <soap:envelope>
        <soap:body>
            <xElement>5</xElement>
            <yElement>5.0</yElement>
        </soap:body>
    </soap:envelope>

    關(guān)于消息組成部分的注意事項
    我本來可以只更改綁定,就像我從 RPC/編碼轉(zhuǎn)到 RPC/所做的那樣。它將是合法的 WSDL。然而,WS-I 基本概要(WS-I Basic Profile)規(guī)定文檔/文字的消息的組成部分引用元素而不是類型,所以我遵循了 WS-I(并且此處使用元素部分可以很好地把我們帶到關(guān)于文檔/文字包裝的樣式的討論)。

    下面是這種方法的優(yōu)點和缺點:

    優(yōu)點

    • 沒有類型編碼信息。
    • 您可以在最后用任何 XML 檢驗器檢驗此消息的有效性。soap:body 里面的所有內(nèi)容都定義在 schema 中。
    • 文檔/文字是遵循 WS-I 的,但是有限制(參閱缺點)。

    缺點

    • WSDL 有一點復雜。不過,這是一個非常小的缺點,因為 WSDL 并沒有打算由人來讀取。
    • SOAP 消息中缺少操作名。而如果沒有操作名,發(fā)送就可能比較困難,并且有時變得不可能。
    • WS-I 僅僅允許 SOAP 消息中 soap:body 的一個子元素。正如你在清單 7 中所見的那樣,該消息的 soap:body 有兩個子元素。

    文檔/文字樣式似乎只是重新排列了一下 RPC/文字模型中的優(yōu)點和缺點。你可以驗證該消息,但是你已經(jīng)失去了操作名。有沒有什么辦法可以改進這一點呢?是的,它就是文檔/文字包裝模式。

    文檔/文字包裝模式
    在我描述文檔/文字包裝模式的規(guī)則之前,讓我先向您展示 WSDL 和 SOAP 消息,如清單 8清單 9 所示。

    清單 8. 用于 myMethod 的文檔/文字封裝的 WSDL。
    <types>
        <schema>
            <element name="myMethod">
                <complexType>
                    <sequence>
                        <element name="x" type="xsd:int"/>
                        <element name="y" type="xsd:float"/>
                    </sequence>
                </complexType>
            </element>
            <element name="myMethodResponse">
                <complexType/>
            </element>
        </schema>
    </types>
    <message name="myMethodRequest">
        <part name="parameters" element="myMethod"/>
    </message>
    <message name="empty">
        <part name="parameters" element="myMethodResponse"/>
    </message>
    
    <portType name="PT">
        <operation name="myMethod">
            <input message="myMethodRequest"/>
            <output message="empty"/>
        </operation>
    </portType>
    
    <binding .../>  
    <!-- I won't bother with the details, just assume it's document/literal. -->

    WSDL Schema 現(xiàn)在把參數(shù)放在包裝中(參閱清單 9)。

    清單 9. 用于 myMethod 的文檔/文字包裝的 SOAP 消息
    <soap:envelope>
        <soap:body>
            <myMethod>
                <x>5</x>
                <y>5.0</y>
            </myMethod>
        </soap:body>
    </soap:envelope>

    注意這個 SOAP 消息同 RPC/文字的 SOAP 消息(清單 5)非常相似。您可能會說,它看起來與 RPC/文字的 SOAP 消息是完全一樣的,不過,這兩種消息之間存在著微妙的區(qū)別。在 RPC/文字的 SOAP 消息中,<soap:body><myMethod> 子句是操作的名稱。在文檔/文字包裝的 SOAP 消息中,<myMethod> 子句是單個輸入消息的組成部分引用的元素的名稱。因此,包裝的樣式具有這樣的一個特征,輸入元素的名稱與操作的名稱是相同的。此樣式是把操作名放入 SOAP 消息的一種巧妙方式。

    文檔/文字包裝的樣式的特征有:

    • 輸入消息只有一個組成部分。
    • 該部分是一個元素。
    • 該元素同操作有相同的名稱。
    • 該元素的復雜類型沒有屬性。

    下面是該種方法的優(yōu)缺點:

    優(yōu)點

    • 沒有類型編碼信息。
    • soap:body 中出現(xiàn)的所有內(nèi)容都定義在 schema 中,所以您可以很容易地檢驗此消息的有效性。
    • 方法名又出現(xiàn)在 SOAP 消息中。
    • 文檔/文字是遵守 WS-I 的,并且包裝模式符合了 WS-I 的限制,即 SOAP 消息的 soap:body 只有一個子元素。

    缺點

    • WSDL 更加復雜。

    文檔/文字包裝的樣式還是有一些缺點,不過與優(yōu)點比起來,它們都顯得微不足道。

    RPC/文字包裝?
    從 WSDL 的角度來考慮,沒有理由只是把把包裝的樣式和文檔/文字綁定聯(lián)系在一起。它可以很容易地應用于 RPC/文字綁定。但是這樣做是相當不明智的。SOAP 將包含操作的一個 myMethod 元素和元素名稱的子 myMethod 元素。另外,即使它是一個合法的 WSDL,RPC/文字元素部分也不遵循 WS-I。

    文檔/文字的樣式在哪里定義?
    這種包裝類型來源于 Microsoft?。并沒有任何規(guī)范來定義這個類型;因此雖然這個類型是一個好東西,但不幸的是,為了與 Microsoft 和其他公司的實現(xiàn)進行互操作,現(xiàn)在惟一的選擇就是根據(jù) Microsoft WSDL 的輸出來猜測它是如何工作的。該模式已經(jīng)出現(xiàn)了一段時間,并且業(yè)界也很好的理解了它,雖然該模式在例子中是非常明顯的,但是也有一些內(nèi)容不夠清晰。我們希望一個獨立的組織比如 WS-I 來幫助穩(wěn)定和標準化這一模式。

    為什么不始終采用文檔/文字包裝的樣式
    至此,本文已經(jīng)給了您這樣的一個印象,文檔/文字包裝的樣式是最好的方法。而實際的情況往往確實如此。不過,仍然存在著一些情況,在這些情況下,您最好是換一種別的樣式。

    采用文檔/文字非包裝的樣式的理由
    如果您已經(jīng)重載了操作,就不能采用文檔/文字包裝的樣式。

    想象一下,除了我們一直在使用的方法之外,還有另一種方法,請參見清單 10

    清單 10. 用于文檔/文字包裝的有問題的方法
    public void myMethod(int x, float y);
    public void myMethod(int x);

    關(guān)于重載的操作的注意事項
    WSDL 2.0 不會允許重載的操作。這對于一些允許該操作的語言(比如 Java)來說是不幸的。一些規(guī)范(比如 JAX-RPC)將不得不定義一個名稱轉(zhuǎn)換模式(name mangling scheme)來將重載的方法映射到 WSDL 中。WSDL 2.0 只不過將問題從 WSDL-to-SOAP 映射轉(zhuǎn)移到 WSDL-to-language 映射中。

    WSDL 允許重載的操作。但是當你向 WSDL 上添加包裝模式的時候,需要元素有與操作相同的名稱,并且在 XML 中不能有兩個名稱相同的元素。所以您必須采用文檔/文字非包裝的樣式或某種 RPC 樣式。

    采用 RPC/文字的樣式的理由
    由于文檔/文字非包裝的樣式?jīng)]有提供操作名,所以在有些情況下,您將需要采用某種 RPC 樣式。比如說清單 11 中的一組方法。





    清單 11. 用于文檔/文字非包裝的樣式的問題方法
    public void myMethod(int x, float y);
    public void myMethod(int x);
    public void someOtherMethod(int x, float y);

    現(xiàn)在假設你的服務器接收到了文檔/文字的 SOAP 消息,你可以回頭看一下清單 7。服務器應該發(fā)送哪一種方法呢?所有您能確切知道的就是,它一定不是 myMethod(int x),因為消息有兩個參數(shù),而這種方法只需要一個參數(shù)。它可能是其他兩種方法中的一種。采用文檔/文字的樣式,您沒有辦法知道是哪一種方法。

    假定服務器接收到一個 RPC/文字的消息,而不是文檔/文字的消息,如清單 5 所示。對于這種消息,服務器很容易決定把它發(fā)送到哪一種方法。你知道該操作名稱是 myMethod,并且你知道你有兩個參數(shù),因此肯定是 myMethod(int x, float y)

    采用 RPC/編碼的理由
    使用 RPC/編碼樣式最重要的原因是為了數(shù)據(jù)圖表。設想你有一個二進制樹,如清單 12 所示。

    清單 12. 二進制樹節(jié)點 schema
    <complexType name="Node">
        <sequence>
            <element name="name" type="xsd:string"/>
            <element name="left" type="Node" xsd:nillable="true"/>
            <element name="right" type="Node" xsd:nillable="true"/>
        </sequence>
    </complexType>

    根據(jù)這種節(jié)點定義,你可以構(gòu)建一個樹,其根節(jié)點 -- A -- 通過左/右鏈接指向節(jié)點 B(參閱圖 1)。

    圖 1. 編碼樹。
    編碼樹

    發(fā)送數(shù)據(jù)圖表的標準方式是使用 href 標簽,它是 RPC/編碼的樣式(清單 13)的一部分。

    清單 13. RPC/編碼的二進制樹
    <A>
        <name>A</name>
        <left href="12345"/>
        <right href="12345"/>
    </A>
    <B id="12345">
        <name>B</name>
        <left xsi:nil="true"/>
        <right xsi:nil="true"/>
    </B>

    在任何文字樣式中,href 屬性都是不可用的,這樣圖形鏈接就不再起作用了(參閱清單 14圖 2)。你仍然有一個根節(jié)點 A,其指向左邊的節(jié)點 B和右邊的另一個節(jié)點 B。這些節(jié)點 B 都是一樣的,但是它們不是相同的節(jié)點。數(shù)據(jù)被復制而不是引用兩次。

    清單 14. 文字二進制樹
    <A>
        <name>A</name>
        <left>
            <name>B</name>
            <left xsi:nil="true"/>
            <right xsi:nil="true"/>
        </left>
        <right>
            <name>B</name>
            <left xsi:nil="true"/>
            <right xsi:nil="true"/>
        </right>
    </A>

    圖 2. 文字樹
    文字樹

    在文字樣式中,您可以通過各種方法構(gòu)造圖表,但是卻沒有標準的方法;所以您做的任何事情很可能不能與網(wǎng)絡中其他端點上的服務進行互操作。

    SOAP 響應消息
    到目前為止我已經(jīng)討論了請求消息。但是響應消息呢?它們是怎樣的呢?現(xiàn)在你應該很清楚一個文檔/文字消息的響應消息應該是怎樣的。soap:body 的內(nèi)容是由 schema 定義的,因此你所需要做的就是查看該 schema 來了解響應消息的內(nèi)容。比如,參考清單 15 來查看清單 8 中的 WSDL 文件的響應消息。

    清單 15. 用于 myMethod 的文檔/文字包裝的響應 SOAP 消息
    <soap:envelope>
        <soap:body>
            <myMethodResponse/>
        </soap:body>
    </soap:envelope>

    但是用于 RPC 樣式響應的 soap:body 的子元素是什么呢?WSDL 1.1 規(guī)范并不是很清楚。但是 WS-I 解決了這個問題。WS-I 的 Basic Profile 指明了在 RPC/文字響應消息中,soap:body 子元素的名稱是“... 相應的 wsdl:operation 名稱加上字符串 'Response' 作為后綴。”奇怪!這正是常規(guī)包裝模式的響應元素的名稱。因此清單 15 可以應用到 RPC/文字消息和文檔/文字包裝的消息。(因為 RPC/編碼并不是遵守 WS-I 的,WS-I Basic Profile 并不關(guān)心 RPC/編碼的響應是怎樣的,但是你可以假設應用在這里的約定也可以應用在其他任何地方。)因此響應消息的內(nèi)容并不神秘。

    結(jié)束語
    這里有四種綁定樣式(其實是五個,但是文檔/編碼的樣式是沒有意義的)。雖然每種樣式都有自己的用處,但是在大多數(shù)情況下,最好的樣式是文檔/文字包裝的樣式。

    參考資料

    關(guān)于作者
    Russell Butek 是 IBM 的一名 Web 服務顧問。他是 IBM WebSphere Web 服務引擎的開發(fā)人員之一。他也是 JAX-RPC Java Specification Request (JSR) 專家組的 IBM 代表。他從事 Apache 的 AXIS SOAP 引擎的實現(xiàn)方面的研究,推動了 AXIS 1.0 遵循 JAX-RPC 1.0。以前,他是 IBM CORBA ORB 的開發(fā)人員和許多 OMG 特別工作組的 IBM 代表:包括可移植攔截器特別工作組(他是這個特別工作組的主席)、核心特別工作組以及互操作性特別工作組。你可以通過 butek@us.ibm.com 與他聯(lián)系。

    posted @ 2005-12-02 13:36 GHawk 閱讀(671) | 評論 (0)編輯 收藏

    FrameBuffer & BootSplash

    http://www.chinaitpower.com/2005September/2005-09-13/192844.html

    posted @ 2005-11-18 11:18 GHawk 閱讀(321) | 評論 (0)編輯 收藏

    Windows 和 Linux 實現(xiàn)相同功能的軟件對照表

    http://cathayan.org/equivalentsoft-zh-cn.html

    posted @ 2005-11-18 10:58 GHawk 閱讀(450) | 評論 (0)編輯 收藏

    安裝圖形界面(X11)中鼠標主題 (轉(zhuǎn))

    From: http://www.gnome-cn.org/documents/howto/install-mouse-theme

    安裝圖形界面(X11)中鼠標主題。
    1. 如果沒有 ~/.icons 目錄,創(chuàng)建它。
    2. 如果沒有 ~/.icons/default 目錄,創(chuàng)建它。
    3. 把鼠標主題解壓到 ~/.icons 下:
         > ls ~/.icons/
         DeepSky  default
      
    4. 創(chuàng)建文件 ~/.icons/default/index.theme , 內(nèi)容如下:
         [Icon Theme]
         Inherits=DeepSky
      
    5. 注消后重新登陸即可看到新鼠標主題。

    posted @ 2005-11-09 10:50 GHawk 閱讀(732) | 評論 (1)編輯 收藏

    搬家

    CSDN那邊總是不太可靠,決定搬過來了:)

    posted @ 2005-11-04 11:15 GHawk 閱讀(159) | 評論 (0)編輯 收藏

    僅列出標題
    共3頁: 上一頁 1 2 3 
    主站蜘蛛池模板: 怡红院亚洲红怡院在线观看| 亚洲av无码专区国产乱码在线观看 | 免费在线观看黄网| 日韩成人在线免费视频| 四色在线精品免费观看| 波多野结衣久久高清免费| 日韩在线免费电影| 四虎永久成人免费| 亚洲色偷拍区另类无码专区| 国产亚洲精久久久久久无码AV| 国产亚洲一区二区三区在线不卡| 亚洲熟女一区二区三区| 亚洲av伊人久久综合密臀性色| 久久伊人久久亚洲综合| 亚洲成年人电影网站| 亚洲 暴爽 AV人人爽日日碰| 亚洲日韩精品无码专区加勒比☆ | 成人亚洲性情网站WWW在线观看| 亚洲一本大道无码av天堂| 亚洲一区二区三区无码中文字幕| 亚洲AV无码日韩AV无码导航| 亚洲精品视频专区| 亚洲成年网站在线观看| 国产成人亚洲综合a∨| 久久久精品视频免费观看| 国产精品网站在线观看免费传媒| 99re免费视频| 成人au免费视频影院| 亚洲 另类 无码 在线| 亚洲午夜福利AV一区二区无码| 亚洲小视频在线观看| 亚洲最大的成人网站| 少妇亚洲免费精品| 国产精品免费大片| 我想看一级毛片免费的| 亚洲精品国产成人影院| 久久久久亚洲Av无码专| 亚洲va中文字幕| av永久免费网站在线观看| 女人被男人桶得好爽免费视频| 亚洲精品无码av天堂|