開始使用Commons Chain (第一部分)
作為程序開發(fā)人員,我們經(jīng)常需要對一個(gè)實(shí)際上程序性的系統(tǒng)應(yīng)用面向?qū)ο蟮姆椒āI虡I(yè)分析家和管理人員描述這樣的系統(tǒng)時(shí)通常不使用類層次和序列圖,而是使用流程圖和工作流圖表。但是不論如何,使用面向?qū)ο蟮姆椒ń鉀Q這些問題時(shí)會帶來更多的靈活性。面向?qū)ο蟮脑O(shè)計(jì)模式提供了有用的結(jié)構(gòu)和行為來描述這種順序的處理,比如模版方法(Template Method)[GoF]和責(zé)任鏈(Chain of Responsibility)[GoF]。
Jakarta Commons的子項(xiàng)目Chain將上述兩個(gè)模式組合成一個(gè)可復(fù)用的Java框架用于描述順序的處理流程。這個(gè)在Jakarta Commons project社區(qū)中開發(fā)的框架,已經(jīng)被廣泛的接受并且使用于許多有趣的應(yīng)用中,特別的是他被Struts和Shale應(yīng)用框架作為處理HTTP請求處理的基礎(chǔ)機(jī)制。你可以在需要定義和執(zhí)行一組連續(xù)的步驟時(shí)使用Commons Chain。
至于經(jīng)典設(shè)計(jì)模式,開發(fā)者和架構(gòu)師普遍使用模版方法(Template Method)造型順序處理。模版方法(Template Method)中使用一個(gè)抽象的父類定義使用的算法:處理的步驟,具體實(shí)現(xiàn)交給子類。當(dāng)然,父類也可以為算法所使用的方法提供一個(gè)缺省實(shí)現(xiàn)。
由于模版方法(Template Method)依賴?yán)^承——子類必須繼承定義了算法的父類——因此使用這個(gè)模式的軟件表現(xiàn)出緊耦合而且缺少靈活性。又由于實(shí)現(xiàn)類添加自己的行為前必須擴(kuò)展父類,溝每⑷嗽北幌拗樸誒嗖憒沃校傭拗屏順絳蟶杓頻牧榛钚浴ommons Chain使用配置文件定義算法,在程序運(yùn)行時(shí)解析配置文件,從而很好的解決了這個(gè)問題。
現(xiàn)在來看一下Commons Chain是怎樣工作的,我們從一個(gè)人造的例子開始:二手車銷售員的商業(yè)流程。下面是銷售流程的步驟:
1.????????得到用戶信息
2.????????試車
3.????????談判銷售
4.????????安排財(cái)務(wù)
5.????????結(jié)束銷售
現(xiàn)在假設(shè)使用模版方法(Template Method)造型這個(gè)流程。首先建立一個(gè)定義了算法的抽象類:
清單1
現(xiàn)在來看一下怎樣用Commons Chain實(shí)現(xiàn)這個(gè)流程。首先,下載Commons Chain。你可以直接下載最新的zip或tar文件,也可以從CVS或者SubVersion源碼庫檢出Commons Chain模塊得到最新的代碼。解壓縮打包文件,將commons-chain.jar放入你的classpath中。
使用Commons Chain實(shí)現(xiàn)這個(gè)商業(yè)流程,必須將流程中的每一步寫成一個(gè)類,這個(gè)類需要有一個(gè)public的方法execute()。這和傳統(tǒng)的命令模式(Command pattern)實(shí)現(xiàn)相同。下面簡單實(shí)現(xiàn)了“得到用戶信息”:
清單2
由于只是演示,這個(gè)類并沒有做很多工作。這里將用戶名放入了Context對象ctx中。這個(gè)Context對象連接了各個(gè)命令。暫時(shí)先將這個(gè)對象想象成根據(jù)關(guān)鍵字存取值的哈希表。所有后來的命令可以通過它訪問剛才放入的用戶名。TestDriveVehicle,NegotiateSale和 ArrangeFinancing命令的實(shí)現(xiàn)只是簡單的打印了將執(zhí)行什么操作。
清單3
CloseSale從Context對象中取出GetCustomerInfo放入的用戶名,并將其打印。
清單4
現(xiàn)在你可以將這個(gè)流程定義成一個(gè)序列(或者說“命令鏈”)。
清單5
運(yùn)行這個(gè)類將會輸出以下結(jié)果:
Get customer info
Test drive the vehicle
Negotiate sale
Arrange financing
Congratulations George Burdell, you bought a new car!
在進(jìn)一步深入之前,讓我們來看一下我們使用了的Commons Chain的類和接口。
Command 類和Chain類的關(guān)系就是組合模式(Composite pattern)[GoF]的例子:Chain不僅由多個(gè)Command組成,而且自己也是Command。這使你可以非常簡單得將單個(gè)命令(Command)替換成由多個(gè)命令(Command)組成的鏈(Chain)。這個(gè)由Command對象唯一操作定義的方法代表了一個(gè)直接的命令:
public boolean execute(Context context);
參數(shù)context僅僅是一個(gè)存放了名稱-值對的集合。接口Context在這里作為一個(gè)標(biāo)記接口:它擴(kuò)展了java.util.Map但是沒有添加任何特殊的行為。于此相反,類ContextBase不僅提供了對Map的實(shí)現(xiàn)而且增加了一個(gè)特性:屬性-域透明。這個(gè)特性可以通過使用Map的put和get 方法操作JavaBean的域,當(dāng)然這些域必須使用標(biāo)準(zhǔn)的getFoo和setFoo方法定義。那些通過JavaBean的“setter”方法設(shè)置的值,可以通過對應(yīng)的域名稱,用Map的get方法得到。同樣,那些用Map的put方法設(shè)置的值可以通過JavaBean的“getter”方法得到。
例如,我們可以創(chuàng)建一個(gè)專門的context提供顯式的customerName屬性支持。
清單6
現(xiàn)在你既可以進(jìn)行Map的一般屬性存取操作同時(shí)也可以使用顯式的JavaBean的訪問和修改域的方法,這兩個(gè)將產(chǎn)生同樣的效果。但是首先你需要在運(yùn)行SellVehicleChain時(shí)實(shí)例化SellVehiceContext而不是ContextBase。
清單7
盡管你不改變GetCustomerInfo中存放用戶名的方法——仍然使用ctx.put("customerName", "George Burdell")——你可以在CloseSale中使用getCustomerName()方法得到用戶名。
清單8
那些依賴類型安全和context的顯式域的命令(Command)可以利用標(biāo)準(zhǔn)的getter和setter方法。當(dāng)一些新的命令(Command)被添加時(shí),它們可以不用考慮context的具體實(shí)現(xiàn),直接通過Map的get和put操作屬性。不論采用何種機(jī)制,ContextBase類都可以保證命令(Command)間可以通過context互操作。
下面這個(gè)例子展示了如何使用Commons Chain的API建立并執(zhí)行一組順序的命令。當(dāng)然,和現(xiàn)在大多數(shù)Java軟件一樣,Commons Chain可以使用XML文件作為配置文件。你可以將“汽車銷售”流程的步驟在XML文件中定義。這個(gè)文件有個(gè)規(guī)范的命名chain- config.xml。
清單9
Chain的配置文件可以包含多個(gè)鏈定義,這些鏈定義可以集合進(jìn)不同的編目中。在這個(gè)例子中,鏈定義在一個(gè)默認(rèn)的編目中定義。事實(shí)上,你可以在這個(gè)文件中定義多個(gè)名字的編目,每個(gè)編目可擁有自己的鏈組。
現(xiàn)在你可以使用Commons Chain提供的類載入編目并得到指定的鏈,而不用像SellVehicleChain中那樣自己在程序中定義一組命令:
清單10
Chain 使用Commons Digester來讀取和解析配置文件。因此你需要將Commons Digester.jar加入classpath中。我使用了1.6版本并且工作得很好。Digester使用了Commons Collectios(我使用的版本是3.1),Commons Logging(版本1.0.4),Commons BeanUtils(1.7.0),因此你也需要將它們的jar文件加入classpath中。在加入這些jar后,CatalogLoader就可以被編譯和運(yùn)行,它的輸出和另外兩個(gè)測試完全相同。
現(xiàn)在你可以在XML文件中定義鏈,并可以在程序中得到這個(gè)鏈(別忘了鏈也是命令),這樣擴(kuò)展的可能性和程序的靈活性可以說是無限的。假設(shè)過程“安排財(cái)務(wù)”實(shí)際上由一個(gè)完全分離的商業(yè)部門處理。這個(gè)部門希望為這種銷售建立自己的工作流程。 Chain提供了嵌套鏈來實(shí)現(xiàn)這個(gè)要求。因?yàn)殒湵旧砭褪敲睿虼四憧梢杂弥赶蛄硪粋€(gè)鏈的引用替換一個(gè)單一用途的命令。下面是增加了新流程的鏈的定義:
清單11
Commons Chain提供了一個(gè)常用的命令LookupCommand來查找和執(zhí)行另一個(gè)鏈。屬性optional用于控制當(dāng)指定的嵌套鏈沒有找到時(shí)如何處理。 optional=true時(shí),即使鏈沒找到,處理也會繼續(xù)。反之,LookupCommand將拋出 IllegalArgumentException,告知指定的命令未找到。
在下面三種情況下,命令鏈將結(jié)束:
1.????????命令的execute方法返回true
2.????????運(yùn)行到了鏈的盡頭
3.????????命令拋出異常
當(dāng)鏈完全處理完一個(gè)過程后,命令就返回true。這是責(zé)任鏈模式(Chain of Responsibility)的基本概念。處理從一個(gè)命令傳遞到另一個(gè)命令,直到某個(gè)命令(Command)處理了這個(gè)命令。如果在到達(dá)命令序列盡頭時(shí)仍沒有處理返回true,也假設(shè)鏈已經(jīng)正常結(jié)束。
當(dāng)有命令拋出錯(cuò)誤時(shí)鏈就會非正常結(jié)束。在Commons Chain中,如果有命令拋出錯(cuò)誤,鏈的執(zhí)行就會中斷。不論是運(yùn)行時(shí)錯(cuò)誤(runtime exception)還是應(yīng)用錯(cuò)誤(application exception),都會拋出給鏈的調(diào)用者。但是許多應(yīng)用都需要對在命令之外定義的錯(cuò)誤做明確的處理。Commons Chain提供了Filter接口來滿足這個(gè)要求。Filter繼承了Command,添加了一個(gè)名為postprocess的方法。
public boolean postprocess(Context context, Exception exception);
只要Filter的execute方法被調(diào)用,不論鏈的執(zhí)行過程中是否拋出錯(cuò)誤,Commons Chain都將保證Filter的postprocess方法被調(diào)用。和servlet的過濾器(filter)相同,Commons Chain的Filter按它們在鏈中的順序依次執(zhí)行。同樣,F(xiàn)ilter的postprocess方法按倒序執(zhí)行。你可以使用這個(gè)特性實(shí)現(xiàn)自己的錯(cuò)誤處理。下面是一個(gè)用于處理我們例子中的錯(cuò)誤的Filter:
清單12
Filter在配置文件中的定義就和普通的命令(Command)定義相同:
清單13
Filter 的execute方法按定義的序列調(diào)用。然而,它的postprocess方法將在鏈執(zhí)行完畢或拋出錯(cuò)誤后執(zhí)行。當(dāng)一個(gè)錯(cuò)誤被拋出時(shí), postprocess方法處理完后會返回true,表示錯(cuò)誤處理已經(jīng)完成。鏈的執(zhí)行并不會就此結(jié)束,但是本質(zhì)上來說這個(gè)錯(cuò)誤被捕捉而且不會再向外拋出。如果postprocess方法返回false,那錯(cuò)誤會繼續(xù)向外拋出,然后鏈就會非正常結(jié)束。
讓我們假設(shè)ArrangeFinancing因?yàn)橛脩粜庞每〒p壞拋出錯(cuò)誤。SellVehicleExceptionHandler就能捕捉到這個(gè)錯(cuò)誤,程序輸出如下:
Filter.execute() called.
Get customer info
Test drive the vehicle
Negotiate sale
Exception Bad credit occurred.
結(jié)合了過濾器(filter)和子鏈技術(shù)后,你就可以造型很復(fù)雜的工作流程。
Commons Chain是一個(gè)很有前途的框架,現(xiàn)在仍在開發(fā),新的功能被頻繁地添加到其中。在下一篇關(guān)于Commons Chain的文章中,我們將研究Struts 1.3中是如何使用Commons Chain的。
Struts 1.3中用完全使用Commons Chain的類替換了原來的處理HTTP請求的類。如果你以前自己定制過Struts的請求處理(request processor),你將發(fā)現(xiàn)處理這個(gè)問題時(shí)Commons Chain為程序帶來了很好的靈活性。
Jakarta Commons的子項(xiàng)目Chain將上述兩個(gè)模式組合成一個(gè)可復(fù)用的Java框架用于描述順序的處理流程。這個(gè)在Jakarta Commons project社區(qū)中開發(fā)的框架,已經(jīng)被廣泛的接受并且使用于許多有趣的應(yīng)用中,特別的是他被Struts和Shale應(yīng)用框架作為處理HTTP請求處理的基礎(chǔ)機(jī)制。你可以在需要定義和執(zhí)行一組連續(xù)的步驟時(shí)使用Commons Chain。
至于經(jīng)典設(shè)計(jì)模式,開發(fā)者和架構(gòu)師普遍使用模版方法(Template Method)造型順序處理。模版方法(Template Method)中使用一個(gè)抽象的父類定義使用的算法:處理的步驟,具體實(shí)現(xiàn)交給子類。當(dāng)然,父類也可以為算法所使用的方法提供一個(gè)缺省實(shí)現(xiàn)。
由于模版方法(Template Method)依賴?yán)^承——子類必須繼承定義了算法的父類——因此使用這個(gè)模式的軟件表現(xiàn)出緊耦合而且缺少靈活性。又由于實(shí)現(xiàn)類添加自己的行為前必須擴(kuò)展父類,溝每⑷嗽北幌拗樸誒嗖憒沃校傭拗屏順絳蟶杓頻牧榛钚浴ommons Chain使用配置文件定義算法,在程序運(yùn)行時(shí)解析配置文件,從而很好的解決了這個(gè)問題。
現(xiàn)在來看一下Commons Chain是怎樣工作的,我們從一個(gè)人造的例子開始:二手車銷售員的商業(yè)流程。下面是銷售流程的步驟:
1.????????得到用戶信息
2.????????試車
3.????????談判銷售
4.????????安排財(cái)務(wù)
5.????????結(jié)束銷售
現(xiàn)在假設(shè)使用模版方法(Template Method)造型這個(gè)流程。首先建立一個(gè)定義了算法的抽象類:
清單1
public abstract class SellVehicleTemplate {
????????public void sellVehicle() {
????????getCustomerInfo();
????????testDriveVehicle();
????????negotiateSale();
????????arrangeFinancing();
????????closeSale();
????????}
????????public abstract void getCustomerInfo();
????????public abstract void testDriveVehicle();
????????public abstract void negotiateSale();
????????public abstract void arrangeFinancing();
????????public abstract void closeSale();????????
}
現(xiàn)在來看一下怎樣用Commons Chain實(shí)現(xiàn)這個(gè)流程。首先,下載Commons Chain。你可以直接下載最新的zip或tar文件,也可以從CVS或者SubVersion源碼庫檢出Commons Chain模塊得到最新的代碼。解壓縮打包文件,將commons-chain.jar放入你的classpath中。
使用Commons Chain實(shí)現(xiàn)這個(gè)商業(yè)流程,必須將流程中的每一步寫成一個(gè)類,這個(gè)類需要有一個(gè)public的方法execute()。這和傳統(tǒng)的命令模式(Command pattern)實(shí)現(xiàn)相同。下面簡單實(shí)現(xiàn)了“得到用戶信息”:
清單2
package com.jadecove.chain.sample;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
public class GetCustomerInfo implements Command {
????????public boolean execute(Context ctx) throws Exception {
????????????????System.out.println("Get customer info");
????????????????ctx.put("customerName","George Burdell");
????????????????return false;
????????}
}
由于只是演示,這個(gè)類并沒有做很多工作。這里將用戶名放入了Context對象ctx中。這個(gè)Context對象連接了各個(gè)命令。暫時(shí)先將這個(gè)對象想象成根據(jù)關(guān)鍵字存取值的哈希表。所有后來的命令可以通過它訪問剛才放入的用戶名。TestDriveVehicle,NegotiateSale和 ArrangeFinancing命令的實(shí)現(xiàn)只是簡單的打印了將執(zhí)行什么操作。
清單3
package com.jadecove.chain.sample;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
public class TestDriveVehicle implements Command {
????????public boolean execute(Context ctx) throws Exception {
????????????????System.out.println("Test drive the vehicle");
????????????????return false;
????????}
}
public class NegotiateSale implements Command {
????????public boolean execute(Context ctx) throws Exception {
????????????????System.out.println("Negotiate sale");
????????????????return false;
????????}
}
public class ArrangeFinancing implements Command {
????????public boolean execute(Context ctx) throws Exception {
????????????????System.out.println("Arrange financing");
????????????????return false;
????????}
}
CloseSale從Context對象中取出GetCustomerInfo放入的用戶名,并將其打印。
清單4
package com.jadecove.chain.sample;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
public class CloseSale implements Command {
????????public boolean execute(Context ctx) throws Exception {
????????????????System.out.println("Congratulations "
??????????????????+ctx.get("customerName")
????????????????????????+", you bought a new car!");
????????????????return false;
????????}
}
現(xiàn)在你可以將這個(gè)流程定義成一個(gè)序列(或者說“命令鏈”)。
清單5
package com.jadecove.chain.sample;
import org.apache.commons.chain.impl.ChainBase;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.apache.commons.chain.impl.ContextBase;
public class SellVehicleChain extends ChainBase {
????????public SellVehicleChain() {
????????????????super();
????????????????addCommand(new GetCustomerInfo());
????????????????addCommand(new TestDriveVehicle());
????????????????addCommand(new NegotiateSale());
????????????????addCommand(new ArrangeFinancing());
????????????????addCommand(new CloseSale());
????????}
????????public static void main(String[] args) throws Exception {
????????????????Command process = new SellVehicleChain();
????????????????Context ctx = new ContextBase();
????????????????process.execute(ctx);
????????}
}
運(yùn)行這個(gè)類將會輸出以下結(jié)果:
Get customer info
Test drive the vehicle
Negotiate sale
Arrange financing
Congratulations George Burdell, you bought a new car!
在進(jìn)一步深入之前,讓我們來看一下我們使用了的Commons Chain的類和接口。

Command 類和Chain類的關(guān)系就是組合模式(Composite pattern)[GoF]的例子:Chain不僅由多個(gè)Command組成,而且自己也是Command。這使你可以非常簡單得將單個(gè)命令(Command)替換成由多個(gè)命令(Command)組成的鏈(Chain)。這個(gè)由Command對象唯一操作定義的方法代表了一個(gè)直接的命令:
public boolean execute(Context context);
參數(shù)context僅僅是一個(gè)存放了名稱-值對的集合。接口Context在這里作為一個(gè)標(biāo)記接口:它擴(kuò)展了java.util.Map但是沒有添加任何特殊的行為。于此相反,類ContextBase不僅提供了對Map的實(shí)現(xiàn)而且增加了一個(gè)特性:屬性-域透明。這個(gè)特性可以通過使用Map的put和get 方法操作JavaBean的域,當(dāng)然這些域必須使用標(biāo)準(zhǔn)的getFoo和setFoo方法定義。那些通過JavaBean的“setter”方法設(shè)置的值,可以通過對應(yīng)的域名稱,用Map的get方法得到。同樣,那些用Map的put方法設(shè)置的值可以通過JavaBean的“getter”方法得到。
例如,我們可以創(chuàng)建一個(gè)專門的context提供顯式的customerName屬性支持。
清單6
package com.jadecove.chain.sample;
import org.apache.commons.chain.impl.ContextBase;
public class SellVehicleContext extends ContextBase {
????????
????????private String customerName;
????????public String getCustomerName() {
????????????????return customerName;
????????}
????????
????????public void setCustomerName(String name) {
????????????????this.customerName = name;
????????}
}
現(xiàn)在你既可以進(jìn)行Map的一般屬性存取操作同時(shí)也可以使用顯式的JavaBean的訪問和修改域的方法,這兩個(gè)將產(chǎn)生同樣的效果。但是首先你需要在運(yùn)行SellVehicleChain時(shí)實(shí)例化SellVehiceContext而不是ContextBase。
清單7
public static void main(String[] args) throws Exception {
????????????????Command process = new SellVehicleChain();
????????????????Context ctx = new SellVehicleContext();
????????????????process.execute(ctx);
????????}
盡管你不改變GetCustomerInfo中存放用戶名的方法——仍然使用ctx.put("customerName", "George Burdell")——你可以在CloseSale中使用getCustomerName()方法得到用戶名。
清單8
????????public boolean execute(Context ctx) throws Exception {
????????????SellVehicleContext myCtx = (SellVehicleContext) ctx;
????????????System.out.println("Congratulations "
???????????????? ??????????????????+ myCtx.getCustomerName()
??????????????????????????????????+ ", you bought a new car!");
????????????return false;
????????}
那些依賴類型安全和context的顯式域的命令(Command)可以利用標(biāo)準(zhǔn)的getter和setter方法。當(dāng)一些新的命令(Command)被添加時(shí),它們可以不用考慮context的具體實(shí)現(xiàn),直接通過Map的get和put操作屬性。不論采用何種機(jī)制,ContextBase類都可以保證命令(Command)間可以通過context互操作。
下面這個(gè)例子展示了如何使用Commons Chain的API建立并執(zhí)行一組順序的命令。當(dāng)然,和現(xiàn)在大多數(shù)Java軟件一樣,Commons Chain可以使用XML文件作為配置文件。你可以將“汽車銷售”流程的步驟在XML文件中定義。這個(gè)文件有個(gè)規(guī)范的命名chain- config.xml。
清單9
<catalog>
??<chain name="sell-vehicle">
????<command?? id="GetCustomerInfo"
????????className="com.jadecove.chain.sample.GetCustomerInfo"/>
????<command?? id="TestDriveVehicle"
????????className="com.jadecove.chain.sample.TestDriveVehicle"/>
????<command?? id="NegotiateSale"
????????className="com.jadecove.chain.sample.NegotiateSale"/>
????<command?? id="ArrangeFinancing"
????????className="com.jadecove.chain.sample.ArrangeFinancing"/>
????<command?? id="CloseSale"
????????className="com.jadecove.chain.sample.CloseSale"/>
??</chain>
</catalog>
Chain的配置文件可以包含多個(gè)鏈定義,這些鏈定義可以集合進(jìn)不同的編目中。在這個(gè)例子中,鏈定義在一個(gè)默認(rèn)的編目中定義。事實(shí)上,你可以在這個(gè)文件中定義多個(gè)名字的編目,每個(gè)編目可擁有自己的鏈組。
現(xiàn)在你可以使用Commons Chain提供的類載入編目并得到指定的鏈,而不用像SellVehicleChain中那樣自己在程序中定義一組命令:
清單10
package com.jadecove.chain.sample;
import org.apache.commons.chain.Catalog;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.apache.commons.chain.config.ConfigParser;
import org.apache.commons.chain.impl.CatalogFactoryBase;
public class CatalogLoader {
????????private static final String CONFIG_FILE =
????????????????"/com/jadecove/chain/sample/chain-config.xml";
????????private ConfigParser parser;
????????private Catalog catalog;
????????
????????public CatalogLoader() {
????????????????parser = new ConfigParser();
????????}
????????public Catalog getCatalog() throws Exception {
????????????????if (catalog == null) {
????????????????
????????parser.parse(this.getClass().getResource(CONFIG_FILE));????????????????
????????
????????????????}
????????????????catalog = CatalogFactoryBase.getInstance().getCatalog();
????????????????return catalog;
????????}
????????public static void main(String[] args) throws Exception {
????????????????CatalogLoader loader = new CatalogLoader();
????????????????Catalog sampleCatalog = loader.getCatalog();
????????????????Command command = sampleCatalog.getCommand("sell-vehicle");
????????????????Context ctx = new SellVehicleContext();
????????????????command.execute(ctx);
????????}
}
Chain 使用Commons Digester來讀取和解析配置文件。因此你需要將Commons Digester.jar加入classpath中。我使用了1.6版本并且工作得很好。Digester使用了Commons Collectios(我使用的版本是3.1),Commons Logging(版本1.0.4),Commons BeanUtils(1.7.0),因此你也需要將它們的jar文件加入classpath中。在加入這些jar后,CatalogLoader就可以被編譯和運(yùn)行,它的輸出和另外兩個(gè)測試完全相同。
現(xiàn)在你可以在XML文件中定義鏈,并可以在程序中得到這個(gè)鏈(別忘了鏈也是命令),這樣擴(kuò)展的可能性和程序的靈活性可以說是無限的。假設(shè)過程“安排財(cái)務(wù)”實(shí)際上由一個(gè)完全分離的商業(yè)部門處理。這個(gè)部門希望為這種銷售建立自己的工作流程。 Chain提供了嵌套鏈來實(shí)現(xiàn)這個(gè)要求。因?yàn)殒湵旧砭褪敲睿虼四憧梢杂弥赶蛄硪粋€(gè)鏈的引用替換一個(gè)單一用途的命令。下面是增加了新流程的鏈的定義:
清單11
<catalog name="auto-sales">
?? <chain name="sell-vehicle">
???????? <command?? id="GetCustomerInfo"
???????????????? className="com.jadecove.chain.sample.GetCustomerInfo"/>
???????? <command?? id="TestDriveVehicle"
???????????????? className="com.jadecove.chain.sample.TestDriveVehicle"/>
???????? <command?? id="NegotiateSale"
???????????????? className="com.jadecove.chain.sample.NegotiateSale"/>
???????? <command
???????????????? className="org.apache.commons.chain.generic.LookupCommand"
???????????? catalogName="auto-sales"
??????????????????????name="arrange-financing"
??????????????????optional="true"/>
???????? <command?? id="CloseSale"
???????????????? className="com.jadecove.chain.sample.CloseSale"/>
?? </chain>
?? <chain name="arrange-financing">
???????? <command?? id="ArrangeFinancing"
???????????????? className="com.jadecove.chain.sample.ArrangeFinancing"/>
?? </chain>
</catalog>
Commons Chain提供了一個(gè)常用的命令LookupCommand來查找和執(zhí)行另一個(gè)鏈。屬性optional用于控制當(dāng)指定的嵌套鏈沒有找到時(shí)如何處理。 optional=true時(shí),即使鏈沒找到,處理也會繼續(xù)。反之,LookupCommand將拋出 IllegalArgumentException,告知指定的命令未找到。
在下面三種情況下,命令鏈將結(jié)束:
1.????????命令的execute方法返回true
2.????????運(yùn)行到了鏈的盡頭
3.????????命令拋出異常
當(dāng)鏈完全處理完一個(gè)過程后,命令就返回true。這是責(zé)任鏈模式(Chain of Responsibility)的基本概念。處理從一個(gè)命令傳遞到另一個(gè)命令,直到某個(gè)命令(Command)處理了這個(gè)命令。如果在到達(dá)命令序列盡頭時(shí)仍沒有處理返回true,也假設(shè)鏈已經(jīng)正常結(jié)束。
當(dāng)有命令拋出錯(cuò)誤時(shí)鏈就會非正常結(jié)束。在Commons Chain中,如果有命令拋出錯(cuò)誤,鏈的執(zhí)行就會中斷。不論是運(yùn)行時(shí)錯(cuò)誤(runtime exception)還是應(yīng)用錯(cuò)誤(application exception),都會拋出給鏈的調(diào)用者。但是許多應(yīng)用都需要對在命令之外定義的錯(cuò)誤做明確的處理。Commons Chain提供了Filter接口來滿足這個(gè)要求。Filter繼承了Command,添加了一個(gè)名為postprocess的方法。
public boolean postprocess(Context context, Exception exception);
只要Filter的execute方法被調(diào)用,不論鏈的執(zhí)行過程中是否拋出錯(cuò)誤,Commons Chain都將保證Filter的postprocess方法被調(diào)用。和servlet的過濾器(filter)相同,Commons Chain的Filter按它們在鏈中的順序依次執(zhí)行。同樣,F(xiàn)ilter的postprocess方法按倒序執(zhí)行。你可以使用這個(gè)特性實(shí)現(xiàn)自己的錯(cuò)誤處理。下面是一個(gè)用于處理我們例子中的錯(cuò)誤的Filter:
清單12
package com.jadecove.chain.sample;
import org.apache.commons.chain.Context;
import org.apache.commons.chain.Filter;
public class SellVehicleExceptionHandler implements Filter {
????????public boolean execute(Context context) throws Exception {
????????????????System.out.println("Filter.execute() called.");
????????????????return false;
????????}
????????public boolean postprocess(Context context,
???????????????????????????????? Exception exception) {
????????????????if (exception == null) return false;
????????????????System.out.println("Exception "
??????????????????????????????+ exception.getMessage()
??????????????????????????????+ " occurred.");
????????????????return true;
????????}
}
Filter在配置文件中的定義就和普通的命令(Command)定義相同:
清單13
<chain name="sell-vehicle">
??<command?? id="ExceptionHandler"
???? className =
?????????? "com.jadecove.chain.sample.SellVehicleExceptionHandler"/>
??<command?? id="GetCustomerInfo"
??????className="com.jadecove.chain.sample.GetCustomerInfo"/>
Filter 的execute方法按定義的序列調(diào)用。然而,它的postprocess方法將在鏈執(zhí)行完畢或拋出錯(cuò)誤后執(zhí)行。當(dāng)一個(gè)錯(cuò)誤被拋出時(shí), postprocess方法處理完后會返回true,表示錯(cuò)誤處理已經(jīng)完成。鏈的執(zhí)行并不會就此結(jié)束,但是本質(zhì)上來說這個(gè)錯(cuò)誤被捕捉而且不會再向外拋出。如果postprocess方法返回false,那錯(cuò)誤會繼續(xù)向外拋出,然后鏈就會非正常結(jié)束。
讓我們假設(shè)ArrangeFinancing因?yàn)橛脩粜庞每〒p壞拋出錯(cuò)誤。SellVehicleExceptionHandler就能捕捉到這個(gè)錯(cuò)誤,程序輸出如下:
Filter.execute() called.
Get customer info
Test drive the vehicle
Negotiate sale
Exception Bad credit occurred.
結(jié)合了過濾器(filter)和子鏈技術(shù)后,你就可以造型很復(fù)雜的工作流程。
Commons Chain是一個(gè)很有前途的框架,現(xiàn)在仍在開發(fā),新的功能被頻繁地添加到其中。在下一篇關(guān)于Commons Chain的文章中,我們將研究Struts 1.3中是如何使用Commons Chain的。
Struts 1.3中用完全使用Commons Chain的類替換了原來的處理HTTP請求的類。如果你以前自己定制過Struts的請求處理(request processor),你將發(fā)現(xiàn)處理這個(gè)問題時(shí)Commons Chain為程序帶來了很好的靈活性。
posted on 2006-08-22 10:40 Binary 閱讀(781) 評論(0) 編輯 收藏 所屬分類: Apache jakarta