原文:http://phil-xzh.javaeye.com/blog/321536
使用Commons Chain
作为程序开发人员,我们经常需要对一个实际上程序性的系统应用面向对象的方法。商业分析家和管理人员描述这样的系统时通常不使用类层次和序列图,而是使用流程图和工作流图表。但是不论如何,使用面向对象的方法解决这些问题时会带来更多的灵活性。面向对象的设计模式提供了有用的结构和行为来描述这种顺序的处理,比如模版方法(Template Method)[GoF]和责任链(Chain of Responsibility)[GoF]。
Jakarta Commons的子项目Chain将上述两个模式组合成一个可复用的Java框架用于描述顺序的处理流程。这个在Jakarta Commons project社区中开发的框架,已经被广泛的接受并且使用于许多有趣的应用中,特别的是他被Struts和Shale应用框架作为处理HTTP请求处理的基础机制。你可以在需要定义和执行一组连续的步骤时使用Commons Chain。
至于经典设计模式,开发者和架构师普遍使用模版方法(Template Method)造型顺序处理。模版方法(Template Method)中使用一个抽象的父类定义使用的算法:处理的步骤,具体实现交给子类。当然,父类也可以为算法所使用的方法提供一个缺省实现。由于模版方法(Template Method)依赖继承——子类必须继承定义了算法的父类——因此使用这个模式的软件表现出紧耦合而且缺少灵活性。又由于实现类添加自己的行为前必须扩展父类,导致开发人员被限制于类层次中,从而限制了程序设计的灵活性。Commons Chain使用配置文件定义算法,在程序运行时解析配置文件,从而很好的解决了这个问题。
现在来看一下Commons Chain是怎样工作的,我们从一个人造的例子开始:二手车销售员的商业流程。下面是销售流程的步骤:
- 得到用户信息
- 试车
- 谈判销售
- 安排财务
- 结束销售
现在假设使用模版方法(Template Method)造型这个流程。首先建立一个定义了算法的抽象类:
- 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();
- }
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();}
现在来看一下怎样用Commons Chain实现这个流程。首先,下载Commons Chain。你可以直接下载最新的zip或tar文件,也可以从CVS或者SubVersion源码库检出Commons Chain模块得到最新的代码。解压缩打包文件,将commons-chain.jar放入你的classpath中。
使用Commons Chain实现这个商业流程,必须将流程中的每一步写成一个类,这个类需要有一个public的方法execute()。这和传统的命令模式(Command pattern)实现相同。下面简单实现了“得到用户信息”:
- 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;
- }
- }
- 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;
- }
- }
- 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;
- }
- }
- 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);
- }
- }
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); }}
运行这个类将会输出以下结果:
Get customer info
Test drive the vehicle
Negotiate sale
Arrange financing
Congratulations George Burdell, you bought a new car!
public boolean execute(Context context);
参数context仅仅是一个存放了名称-值对的集合。接口Context在这里作为一个标记接口:它扩展了java.util.Map但是没有添加任何特殊的行为。于此相反, 类ContextBase不仅提供了对Map的实现而且增加了一个特性:属性-域透明。这个特性可以通过使用Map的put和get 方法操作JavaBean的域,当然这些域必须使用标准的get*和set*方法定义。那些通过JavaBean的“setter”方法设置的值,可以通过对应的域名称,用Map的get方法得到。同样,那些用Map的put方法设置的值可以通过JavaBean的“getter”方法得到。
- 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;
- }
- }
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; }}
现在你既可以进行Map的一般属性存取操作,同时也可以使用显式的JavaBean的访问和修改域的方法,这两个将产生同样的效果。但是首先你需要在运行SellVehicleChain时实例化SellVehiceContext而不是ContextBase。
- public static void main(String[] args) throws Exception {
- Command process = new SellVehicleChain();
- Context ctx = new SellVehicleContext();
- process.execute(ctx);
- }
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()方法得到用户名。
- public boolean execute(Context ctx) throws Exception {
- SellVehicleContext myCtx = (SellVehicleContext) ctx;
- System.out.println("Congratulations " + myCtx.getCustomerName() + ", you bought a new car!");
- return false;
- }
- <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>
<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的配置文件可以包含多个链定义,这些链定义可以集合进不同的编目中。在这个例子中,链定义在一个默认的编目中定义。事实上,你可以在这个文件中定义多个名字的编目,每个编目可拥有自己的链组。
- 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);
- }
- }
- <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>
在下面三种情况下,命令链将结束:
- 命令的execute方法返回true
- 运行到了链的尽头
- 命令抛出异常
public boolean postprocess(Context context, Exception exception);
只要Filter的execute方法被调用,不论链的执行过程中是否抛出错误,Commons Chain都将保证Filter的postprocess方法被调用。和servlet的过滤器(filter)相同,Commons Chain的Filter按它们在链中的顺序依次执行。同样,Filter的postprocess方法按倒序执行。你可以使用这个特性实现自己的错误处理。下面是一个用于处理我们例子中的错误的Filter:
- 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;
- }
- }
- <chain name="sell-vehicle">
- <command id="ExceptionHandler" className = "com.jadecove.chain.sample.SellVehicleExceptionHandler"/>
- <command id="GetCustomerInfo" className="com.jadecove.chain.sample.GetCustomerInfo"/>
- ......
<chain name="sell-vehicle"> <command id="ExceptionHandler" className = "com.jadecove.chain.sample.SellVehicleExceptionHandler"/> <command id="GetCustomerInfo" className="com.jadecove.chain.sample.GetCustomerInfo"/>
Filter 的execute方法按定义的序列调用。然而,它的postprocess方法将在链执行完毕或抛出错误后执行。当一个错误被抛出时, postprocess方法处理完后会返回true,表示错误处理已经完成。链的执行并不会就此结束,但是本质上来说这个错误被捕捉而且不会再向外抛出。如果postprocess方法返回false,那错误会继续向外抛出,然后链就会非正常结束。
让我们假设ArrangeFinancing因为用户信用卡损坏抛出错误。SellVehicleExceptionHandler就能捕捉到这个错误,程序输出如下:
Filter.execute() called.
Get customer info
Test drive the vehicle
Negotiate sale
Exception Bad credit occurred.
结合了过滤器(filter)和子链技术后,你就可以造型很复杂的工作流程。
public boolean execute(Context ctx) throws Exception { SellVehicleContext myCtx = (SellVehicleContext) ctx; System.out.println("Congratulations " + myCtx.getCustomerName() + ", you bought a new car!"); return false;}