一、前言
Commons-chain是apache commons中的一个子项目,主要被使用在"责任链"的场景中,struts中action的调用过程,就是使用了"chain"框架做支撑.如果你的项目中,也有基于此种场景的需求,可以考虑使用它.
在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。即,在流水线上,属于自己的就处理,不属于自己的就丢给下一个处理。
(具体可以查看菜鸟教程:责任链模式 | 菜鸟教程)
二、 Commons Chain
一、关系图
二、接口详解
一、Command 接口
Chain 中的具体某一步要执行的命令。它只有一个方法:
boolean execute(Context context)
如果返回 true ,那么表示 Chain 的处理结束,
如果返回 false ,那么表示Chain 中的其他命令不会被调用(也就是不执行这个command);然后Chain 会继续调用下一个 Command 。
从开始执行到结束会出现三种情况:
1、 Command 返回 true;
2、Command 抛出异常;
3、一直执行到 Chain 的末尾;
注意:context对象表示当前"责任链"的上下文信息,它可以用来保存一些临时变量(可以在command间共享)
二、 Chain 接口
它表示“命令链”,要在其中执行的命令,需要先添加到 Chain 中。 Chain 的父接口是 Command , ChainBase 实现了它。它有两个方法:
// 可以添加多个command
void addCommand(Command var1);
// ChainBase 执行责任链的时候会调用这个方法,然后这个方法会调用每个command的execute方法去执行。
boolean execute(Context var1) throws Exception;
由于直接继承自
Command
接口,所以Chain也是一种Command。Command 类和Chain类的关系就是组合模式,Chain不仅由多个Command组成,而且自己也是Command。
三、 Context 接口
它表示命令执行的上下文,在命令间实现共享信息的传递。 Context 接口的父接口是 Map , ContextBase 实现了 Context 。
另外观察
ContextBase
的构造函数实现,可以发现ContextBase
在初始化的时候会对本身使用反射进行处理,提取自身的自定义属性,并以键值对的形式推入到自身容器中,这样可以直接通过context.get("beanPropertyName")来获取相应的值。
四、 Filter 接口
当有命令抛出错误时链就会非正常结束。在Commons Chain中,如果有命令抛出错误,链的执行就会中断。不论是运行时错误(runtime exception)还是应用错误(application exception),都会抛出给链的调用者。但是许多应用都需要对在命令之外定义的错误做明确的处理。Commons Chain提供了Filter接口来满足这个要求。
它的父接口是 Command ,它是一种特殊的 Command 。除了 Command 的 execute ,它还包括一个方法:
boolean postprocess(Context context, Exception exception)
Commons Chain 会在执行了 Filter 的 execute 方法之后,执行 postprocess (不论 Chain 以何种方式结束)。
Filter 的执行 execute 的顺序与 Filter 出现在 Chain 中出现的位置一致,但是执行 postprocess 顺序与之相反。
如:如果连续定义了 filter1 和 filter2 ,那么execute 的执行顺序是: filter1 -> filter2 ;而 postprocess 的执行顺序是: filter2 -> filter1 。postprocess 方法只会在链的最后执行。
postprocess方法将在链执行完毕或抛出错误后执行。当一个错误被抛出时, postprocess方法处理完后会返回true,表示错误处理已经完成。链的执行并不会就此结束,但是本质上来说这个错误被捕捉而且不会再向外抛出。如果postprocess方法返回false,那错误会继续向外抛出,然后链就会非正常结束。
五、 Catalog 接口
它是逻辑命名的 Chain 和 Command 集合。通过使用它, Command 的调用者不需要了解具体实现 Command 的类名,只需要通过名字就可以获取所需要的 Command 实例。
比如:你在chain中则增加了Commond
// 增加命令的顺序也决定了执行命令的顺序
public CommandChain(){
addCommand( new Command1());
addCommand( new Command2());
addCommand( new Command3());
}
那你可以通过
Catalog catalog= CatalogFactoryBase.getInstance().getCatalog();
Command cmd= catalog.getCommand( "command1");
获取到对应的command,然后单独执行这个Commmand;
cmd.execute( context);
context:传入的是 Context对象。
三、网上常用的使用方式
public abstract class SellVehicleTemplate {
// 销售汽车
public void sellVehicle() {
testDriveVehicle();
negotiateSale();
arrangeFinancing();
closeSale();
}
// 试车
public abstract void testDriveVehicle();
// 销售谈判
public abstract void negotiateSale();
// 安排财务
public abstract void arrangeFinancing();
// 结束销售
public abstract void closeSale();
}
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
// 试车(继承Command)
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;
}
}
// 结束销售
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;
}
}
// 定义责任链并测试
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;
// 继承ChainBase
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);
}
// 运行结果:
Test drive the vehicle
Negotiate sale
Arrange financing
Congratulations George Burdell, you bought a new car!
Commons Chain提供了配置文件的方式定义责任链,在项目资源目录中创建chain- config.xml文件
<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>
// 从xml配置中读取
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);
}
}