GHawk

          #

          EJB 異常處理的最佳做法

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

          EJB 異常處理的最佳做法

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

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

          將此頁作為電子郵件發送

          未顯示需要 JavaScript 的文檔選項


          對此頁的評價

          幫助我們改進這些內容


          級別: 初級

          Srikanth Shenoy, J2EE 顧問

          2002 年 5 月 05 日

          隨著 J2EE 成為企業開發平臺之選,越來越多基于 J2EE 的應用程序將投入生產。J2EE 平臺的重要組件之一是 Enterprise JavaBean(EJB)API。J2EE 和 EJB 技術一起提供了許多優點,但隨之而來的還有一些新的挑戰。特別是企業系統,其中的任何問題都必須快速得到解決。在本文中,企業 Java 編程老手 Srikanth Shenoy 展現了他在 EJB 異常處理方面的最佳做法,這些做法可以更快解決問題。

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

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

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

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

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

          異常處理基礎知識

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

          異常處理的原則

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

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

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

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

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

          這里的解決方案顯然是使用一個日志實用程序。采用恰當的編碼約定,日志實用程序將負責精確地記錄下任何類型的消息,不論是系統錯誤還是一些警告。所以,我們將在進一步講述之前談談日志實用程序。

          日志領域:鳥瞰

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

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

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

          盡管日志實用程序能幫助我們把消息記錄到適當的持久位置,但它們并不能根除問題。它們不能從產品日志中精確找出某個客戶的問題報告;這一便利技術留給您把它構建到您正在開發的系統中。

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

          異常的類別

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

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

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



          回頁首


          EJB 容器怎樣處理異常

          EJB 容器攔截 EJB 組件上的每一個方法調用。結果,方法調用中發生的每一個異常也被 EJB 容器攔截到。EJB 規范只處理兩種類型的異常:應用程序異常和系統異常。

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

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

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

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

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

          :EJB 1.0 規范要求把受查系統異常作為 RemoteException 拋出。從 EJB 1.1 規范起規定 EJB 實現類絕不應拋出 RemoteException



          回頁首


          常見的異常處理策略

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

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


          清單 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  }
          

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

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

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

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



          回頁首


          EJB 異常處理探試法

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

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

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

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

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


          回頁首


          處理應用程序異常

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

          實體 EJB 組件中的應用程序異常
          清單 2 顯示了實體 EJB 的一個 ejbCreate() 方法。這個方法的調用者傳入一個 OrderItemValue 并請求創建一個 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 的一個很典型的用法。類似地,如果方法的輸入參數的值不正確,則查找程序方法將拋出 FinderException

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

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

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


          清單 3. 會話 EJB 組件中的樣本容器回調方法
          
          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);
              }
          }
          



          回頁首


          處理系統異常

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

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


          清單 4. 處理系統異常的一種常見方式
          
          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);
          }
          

          避免重復記錄

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

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

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

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

          您可以派生 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 ),用于檢查異常是否已經被記錄了。每當捕獲一個 LoggableEJBException 時,看一下該異常是否已經被記錄了( isLogged == false )。如果 isLogged 為 false,則記錄該異常并把標志設置為 true

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

          單 6 顯示了使用這一技術重寫后的清單 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 組件中的系統異常

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

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

          為了把您的應用程序代碼從錯綜復雜的代碼(例如 RemoteException 的代碼)中分離出來,這些行被重新構造成一個稱為 ExceptionLogUtil 的類。有了這個類,您所要做的只是每當需要創建 LoggableEJBException 時調用 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 中看到的是一個非常簡單明了的異常記錄機制。一旦捕獲受查系統異常就創建一個新的 LoggableEJBException 。接著,使用類 StackTraceUtil 獲取 LoggableEJBException 的堆棧跟蹤,把它作為一個字符串。然后,使用 Log4J category 把該字符串作為一個錯誤加以記錄。

          StackTraceUtil 類的工作原理

          在清單 7 中,您看到了一個新的稱為 StackTraceUtil 的類。因為 Log4J 只能記錄 String 消息,所以這個類負責解決把堆棧跟蹤轉換成 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 包含有堆棧跟蹤時,它只是調用 StringWritertoString() ,以獲取該堆棧跟蹤的 String 表示。



          回頁首


          Web 層的 EJB 異常處理

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

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

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



          回頁首


          真實世界的復雜性

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


          清單 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 當應用程序異常對待,但把它當系統異常對待會更好。原因是:EJB 客戶機傾向于把 EJB 組件當黑箱對待。如果 createOrder() 方法的調用者獲得了一個 FinderException ,這對調用者并沒有任何意義。 OrderEJB 正試圖設置客戶遠程引用這件事對調用者來說是透明的。從客戶機的角度看,失敗僅僅意味著該訂單無法創建。

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

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

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

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



          回頁首


          結束語

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

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



          回頁首


          參考資料

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

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


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


          • 您可以閱讀 Sun Microsystems 的 EJB 規范了解關于 EJB 體系結構的更多信息。


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


          • Struts 框架是 Jakarta 項目的另一個珍品。Struts 建立在 MVC 體系結構的基礎上,提供了一個徹底的分離,它把系統的表示層從系統的業務邏輯層中分離出來。


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


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


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


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


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


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


          • 您可以在 developerWorks Java 技術專區找到數以百計關于 Java 編程的方方面面的文章。


          回頁首


          關于作者

          Srikanth Shenoy 的照片

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

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

          正則表達式之道[轉]

          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
          什么是正則表達式

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

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

          正則表達式基礎

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

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

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

          元字符   描述


          .
          匹配任何單個字符。例如正則表達式r.t匹配這些字符串:ratrutr t,但是不匹配root。 
          $
          匹配行結束符。例如正則表達式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或多個正好在它之前的那個字符。例如正則表達式.*意味著能夠匹配任意數量的任何字符。
          \
          這是引用府,用來將這里列出的這些元字符當作普通的字符來進行匹配。例如正則表達式\$被用來匹配美元符號,而不是行尾,類似的,正則表達式\.用來匹配點字符,而不是任何字符的通配符。
          [ ] 
          [c1-c2]
          [^c1-c2]
          匹配括號中的任何一個字符。例如正則表達式r[aou]t匹配ratrotrut,但是不匹配ret。可以在括號中使用連字符-來指定字符的區間,例如正則表達式[0-9]可以匹配任何數字字符;還可以制定多個區間,例如正則表達式[A-Za-z]可以匹配任何大小寫字母。另一個重要的用法是“排除”,要想匹配除了指定區間之外的字符——也就是所謂的補集——在左邊的括號和第一個字符之間使用^字符,例如正則表達式[^269A-Z] 將匹配除了2、6、9和所有大寫字母之外的任何字符。
          \< \>
          匹配詞(word)的開始(\<)和結束(\>)。例如正則表達式\<the能夠匹配字符串"for the wise"中的"the",但是不能匹配字符串"otherwise"中的"the"。注意:這個元字符不是所有的軟件都支持的。
          \( \)
          將 \( 和 \) 之間的表達式定義為“組”(group),并且將匹配這個表達式的字符保存到一個臨時區域(一個正則表達式中最多可以保存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\}
          匹配指定數目的字符,這些字符是在它之前的表達式定義的。例如正則表達式A[0-9]\{3\} 能夠匹配字符"A"后面跟著正好3個數字字符的串,例如A123、A348等,但是不匹配A1234。而正則表達式[0-9]\{4,6\} 匹配連續的任意4個、5個或者6個數字字符。注意:這個元字符不是所有的軟件都支持的。


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

            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,因為正則表達式是大小寫敏感的。要想同時匹配大寫和小寫字母,應該使用字符區間元字符(方括號)。正則表達式[Rr]能夠同時匹配Rr。所以,要想匹配一個大寫或者小寫的r接著任何一個字符再接著一個t就要使用這個表達式:[Rr].t

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

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

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

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

          簡單的例子

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

          vi 命令 作用


          :%s/ */ /g 把一個或者多個空格替換為一個空格。
          :%s/ *$// 去掉行尾的所有空格。
          :%s/^/ / 在每一行頭上加入一個空格。
          :%s/^[0-9][0-9]* // 去掉行首的所有數字字符。
          :%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()的參數。也就是說我們要實現這樣的轉換:

          之前   之后
          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)

          下面這條替換命令能夠實現這一魔法:

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

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

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

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

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

          例2

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

          這里有幾行我們現在的數據:

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

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

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

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

          例3

          假設有一個多字符的片斷重復出現,例如:

          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",以及任意數量連續出現的"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或多個連續的"really "(注意結尾有個空格),而\(really \)\(really \)* 匹配1個或多個連續的"really "實例。

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

          Coming soon.


          不同工具中的正則表達式

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

          當然,你也可以在Visual C++編輯器中使用RE。選擇Edit->Replace,然后選擇"Regular expression"選擇框,Find What輸入框對應上面介紹的vi命令:%s/pat1/pat2/g中的pat1部分,而Replace輸入框對應pat2部分。但是,為了得到vi的執行范圍和g選項,你要使用Replace All或者適當的手工Find Next and Replace(譯者按:知道為啥有人罵微軟弱智了吧,雖然VC中可以選中一個范圍的文本,然后在其中執行替換,但是總之不夠vi那么靈活和典雅)。

          sed

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

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

          sed腳本   描述


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

          awk

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

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

          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列的和(假定為數字)
          awk '$3 !~ /^[0-9]+\.[0-9]*$/ {print $0}' price.txt 打印所有第三個字段不是數字的行,這里數字是指d.d或者d這樣的形式,其中d是0到9的任何數字
          awk '$2 ~ /John|Fred/ {print $0}' price.txt 如果第二個字段包含'John'或者'Fred'則打印整行

          grep

          grep是一個用來在一個或者多個文件或者輸入流中使用RE進行查找的程序。它的name編程語言可以用來針對文件和管道進行處理。可以在手冊中得到關于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結束的行打印出來,注意制表符是用\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之間任一字符,并且點號號碼結尾是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結束或者以W的行
          egrep 'net(work)?s' report.txt 從report.txt中找到所有包含networks或者nets的行


          正則表達式語法支持情況

          命令或環境 . [ ] ^ $ \( \) \{ \} ? + | ( )
          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的命令執行界面。
            range 是命令執行范圍的指定,可以使用百分號(%)表示所有行,使用點(.)表示當前行,使用美元符號($)表示最后一行。你還可以使用行號,例如10,20表示第10到20行,.,$表示當前行到最后一行,.+2,$-5表示當前行后兩行直到全文的倒數第五行,等等。

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

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

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

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

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

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

          運用Jakarta Struts的七大實戰心法

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

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

            一個好的framework有很多優點,首先,它必須能夠滿足用戶的可預見的需求。為此 Struts為Web 應用提供了一個通用的架構,這樣開發人員可以把精力集中在如何解決實際業務問題上。其次,一個好的framework還必須能夠在適當的地方提供擴展接口,以便應用程序能擴展該框架來更好的適應使用者的實際需要。

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

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

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

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

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

            2. 使用異常處理聲明

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            圖 1. 一個典型的商店應用程序的目錄結構
            

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

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

            ? 為每個應用模塊創建獨立的Struts配置文件。

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

            ? 使用org.apache.struts.actions.SwitchAction 來實現程序在模塊之間的跳轉.

            創建獨立的Struts配置文件

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

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

            配置Web部署描述符

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

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

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

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

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

            當Struts載入應用程序時,它首先載入缺省應用模塊的配置文件。然后查找帶有字符串config/xxx 形式的附加的初始化參數。對每個附加的配置文件也進行解析并載入內存。這一步完成后,用戶就可以很隨意地用config/后面的字符串也就是名字來調用相應的應用模塊。

            多個應用模塊之間調用Action類

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

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

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

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

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

            采用前面的例子,圖3顯示將JSP頁面移到WEB-INF 目錄下后的目錄結構

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

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

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

            5. 使用 Prebuilt Action類提升開發效率

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

            使用 ForwardAction

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

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

            正如你看到的,當 /home 被調用時, 就會調用ForwardAction 并把請求轉發到 index.jsp 頁面.

            再討論一下不通過一個Action類直接轉發到某個頁面的情況,必須注意我們仍然使用元素中的forward屬性來實現轉發的目標。這時元素定義如下:

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

            使用 DispatchAction

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

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

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

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

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

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

            6.使用動態ActionForm

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

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

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

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

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

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

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

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

            7. 使用可視化工具

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

            表 1. Struts GUI 工具
            應用程序 性質 網址
            Adalon 商業軟件 http://www.synthis.com/products/adalon
            Easy Struts 開放源碼 http://easystruts.sourceforge.net/
            Struts Console 免費 http://www.jamesholmes.com/struts/console
            JForms 商業軟件 http://www.solanasoft.com/
            Camino 商業軟件 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

            相關資源

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

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

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

          我應該使用哪種樣式的 WSDL 呢?
          內容:
          引言
          RPC/編碼
          RPC/文字
          文檔/編碼
          文檔/文字
          文檔/文字包裝模式
          為什么不始終采用文檔/文字包裝的樣式
          SOAP 響應消息
          結束語
          參考資料
          關于作者
          對本文的評價
          相關內容:
          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 綁定描述了如何把服務綁定到消息傳遞協議(特別是 SOAP 消息傳遞協議)。WSDL SOAP 綁定可以是 RPC 樣式的綁定,也可以是文檔樣式的綁定。同樣,SOAP 綁定可以有編碼的用法,也可以有文字的用法。這給我們提供了四種樣式/用法模型:

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

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

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

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

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

          清單 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. -->

          現在用“5”作為參數 x 的值,“5.0”作為參數 y 的值來調用這個方法。發送一個如清單 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>

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

          • 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/"

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

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

          優點

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



          缺點

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

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

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

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

          清單 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>

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

          這里是這種方法的優點和缺點:

          優點

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

          缺點

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

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

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

          文檔/文字
          文檔/文字的 WSDL 在 RPC/文字的 WSDL 基礎上做了一些修改。其不同點已經在清單 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>

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

          下面是這種方法的優點和缺點:

          優點

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

          缺點

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

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

          文檔/文字包裝模式
          在我描述文檔/文字包裝模式的規則之前,讓我先向您展示 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 現在把參數放在包裝中(參閱清單 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 消息是完全一樣的,不過,這兩種消息之間存在著微妙的區別。在 RPC/文字的 SOAP 消息中,<soap:body><myMethod> 子句是操作的名稱。在文檔/文字包裝的 SOAP 消息中,<myMethod> 子句是單個輸入消息的組成部分引用的元素的名稱。因此,包裝的樣式具有這樣的一個特征,輸入元素的名稱與操作的名稱是相同的。此樣式是把操作名放入 SOAP 消息的一種巧妙方式。

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

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

          下面是該種方法的優缺點:

          優點

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

          缺點

          • WSDL 更加復雜。

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

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

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

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

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

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

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

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

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

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





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

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

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

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

          清單 12. 二進制樹節點 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>

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

          圖 1. 編碼樹。
          編碼樹

          發送數據圖表的標準方式是使用 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)。你仍然有一個根節點 A,其指向左邊的節點 B和右邊的另一個節點 B。這些節點 B 都是一樣的,但是它們不是相同的節點。數據被復制而不是引用兩次。

          清單 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. 文字樹
          文字樹

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

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

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

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

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

          參考資料

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

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

          FrameBuffer & BootSplash

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

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

          Windows 和 Linux 實現相同功能的軟件對照表

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

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

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

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

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

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

          搬家

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

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

          僅列出標題
          共3頁: 上一頁 1 2 3 
          主站蜘蛛池模板: 韶山市| 盐源县| 南和县| 太白县| 大足县| 阿鲁科尔沁旗| 新余市| 华阴市| 明星| 华容县| 洛宁县| 石狮市| 浙江省| 庄河市| 库车县| 铜梁县| 二连浩特市| 瑞昌市| 盐源县| 锡林郭勒盟| 万盛区| 祁门县| 禄丰县| 乐亭县| 五河县| 大余县| 邻水| 察隅县| 北川| 南通市| 南川市| 信宜市| 噶尔县| 纳雍县| 犍为县| 德州市| 宾阳县| 醴陵市| 石首市| 黎川县| 霍州市|