Apache commons chain 是什么
Apache common chain 是对责任链设计模式的改造封装,让使用者更加方便的使用。
简单回顾一下责任链设计模式
在阎宏博士的《JAVA与模式》一书中开头是这样描述责任链(Chain of Responsibility)模式的:
责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任
关键点:
- 链是一系列节点的集合
- 链的各个节点可随意拆分和组装
使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系, 将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。
责任链适用的场景
-
有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
-
你想在不明确指定接受者的情况下,想过个对象中的一个提交一个请求。
-
可处理一个请求的对象集合应该被动态指定。
简单例子
abstract class Handler { private Handler nextHandler; public Handler getNextHandler() { return nextHandler; } public void setNextHandler(Handler nextHandler) { this.nextHandler = nextHandler; } public abstract void doHandler(); } class ConcreteHandler extends Handler { @Override public void doHandler() { if (getNextHandler() != null) { System.out.println("还有责任链"); getNextHandler().doHandler(); } else { System.out.println("我自己处理" + toString()); } } }
设计模式主体架构:
角色:
抽象处理者角色(Handler):定义出一个处理请求的接口。如果需要,接口可以定义 出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。 具体处理者角色(ConcreteHandler):具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。 抽象处理者角色
public abstract class Handler { /** * 持有后继的责任对象 */ protected Handler successor; /** * 示意处理请求的方法,虽然这个示意方法是没有传入参数的 * 但实际是可以传入参数的,根据具体需要来选择是否传递参数 */ public abstract void handleRequest(); /** * 取值方法 */ public Handler getSuccessor() { return successor; } /** * 赋值方法,设置后继的责任对象 */ public void setSuccessor(Handler successor) { this.successor = successor; } }
具体处理者角色
public class ConcreteHandler extends Handler { /** * 处理方法,调用此方法处理请求 */ @Override public void handleRequest() { /** * 判断是否有后继的责任对象 * 如果有,就转发请求给后继的责任对象 * 如果没有,则处理请求 */ if(getSuccessor() != null) { System.out.println("放过请求"); getSuccessor().handleRequest(); }else { System.out.println("处理请求"); } } }
客户端类:
public class Client { public static void main(String[] args) { //组装责任链 Handler handler1 = new ConcreteHandler(); Handler handler2 = new ConcreteHandler(); handler1.setSuccessor(handler2); //提交请求 handler1.handleRequest(); } }
Apache CommonsChain
CommonsChain实现了Chain of Responsebility和Command模式,其中的Catalog + 配置文件的方式使得调用方和Command的实现方的耦合度大大的降低,提高了灵活性。
如何使用
简单写了一个实现:
/** * <p>Test implementation of <code>Chain</code> that exposes the * <code>getCommands()</code> method publicy.</p> */ public class TestChain extends ChainBase { /** * 获取责任链中所有加入的命令 * @return 责任链中的命令 */ public Command[] getCommands() { return (commands); } public static void main(String[] args) throws Exception { TestChain testChain=new TestChain(); Context context=new ContextBase(); /** * 加入执行命令1 */ testChain.addCommand(new Command() { public boolean execute(Context context) throws Exception { System.out.println("执行命令1"); return false; } }); /** * 加入执行命令2 * 注意:返回值为true 表示执行到此为止 */ testChain.addCommand(new Command() { public boolean execute(Context context) throws Exception { System.out.println("执行命令2"); return true; } }); /** * 加入执行命令3 */ testChain.addCommand(new Command() { public boolean execute(Context context) throws Exception { System.out.println("执行命令3"); return false; } }); //执行链中的命令 testChain.execute(context); //打印链中的所有命令 Command[] commands=testChain.getCommands(); for(Command command:commands){ System.out.println(command.getClass()); } } }
** 执行结果 **
- 执行命令1 - 执行命令2 - class org.apache.commons.chain.config.TestChain$1 - class org.apache.commons.chain.config.TestChain$2 - class org.apache.commons.chain.config.TestChain$3
基本对象
-
1. Command接口。它是Commons Chain中最重要的接口,表示在Chain中的具体某一步要执行的命令。它只有一个方法:boolean execute(Context context)。如果返回true,那么表示Chain的处理结束,Chain中的其他命令不会被调用;返回false,则Chain会继续调用下一个Command,直到:
-
[x] > Command返回true;
-
[x] > Command抛出异常;
-
[x] > Chain的末尾;
-
2. Context接口。它表示命令执行的上下文,在命令间实现共享信息的传递。Context接口的父接口是Map,ContextBase实现了Context。对于web环境,可以使用WebContext类及其子类(FacesWebContext、PortletWebContext和ServletWebContext)。
-
3. Chain接口。它表示“命令链”,要在其中执行的命令,需要先添加到Chain中。Chain的父接口是Command,ChainBase实现了它。
使用配置文件
test-config.xml <catalog> <chain name="Execute"> <command id="1" className="org.apache.commons.chain.impl.DelegatingCommand"/> <command id="2" className="org.apache.commons.chain.impl.DelegatingCommand"/> <command id="3" className="org.apache.commons.chain.impl.ExceptionCommand"/> </chain> </catalog>
装入配置文件
public class ConfigParserTestCase extends TestCase { private static final String DEFAULT_XML = "/org/apache/commons/chain/config/test-config.xml"; // ------------------------------------------------------------ Constructors /** * Construct a new instance of this test case. * * @param name Name of the test case */ public ConfigParserTestCase(String name) { super(name); } // ------------------------------------------------------ Instance Variables /** * <p>The <code>Catalog</code> to contain our configured commands.</p> */ protected Catalog catalog = null; /** * <p>The <code>Context</code> to use for execution tests.</p> */ protected Context context = null; /** * <p>The <code>ConfigParser</code> instance under test.</p> */ protected ConfigParser parser = null; // ---------------------------------------------------- Overall Test Methods /** * Set up instance variables required by this test case. */ public void setUp() { catalog = new CatalogBase(); context = new ContextBase(); parser = new ConfigParser(); } /** * Return the tests included in this test suite. */ public static Test suite() { return (new TestSuite(ConfigParserTestCase.class)); } /** * Tear down instance variables required by this test case. */ public void tearDown() { parser = null; context = null; catalog = null; } /** 执行测试方法 **/ public void testExecute2c() throws Exception { load(DEFAULT_XML); try { catalog.getCommand("Execute").execute(context); } catch (ArithmeticException e) { assertEquals("Correct exception id", "3", e.getMessage()); } checkExecuteLog("1/2/3"); } /** 从配置文件中加载配置信息 **/ protected void load(String path) throws Exception { parser.parse(this.getClass().getResource(path)); catalog = CatalogFactoryBase.getInstance().getCatalog(); } }
注意:使用配置文件的话,需要使用Commons Digester。而Digester则依赖:Commons Collections、Commons Logging和Commons BeanUtils。
-
4. 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。
-
5. Catalog接口。它是逻辑命名的Chain和Command集合。通过使用它,Command的调用者不需要了解具体实现Command的类名,只需要通过名字就可以获取所需要的Command实例。
-
6.
的使用。配置文件的引入,使得Commons Chain的灵活性大大的提高。在实际的使用过程中,存在着同一个Command被多个Chain使用的情形。如果每次都书写Command的类名,尤其是前面的包名特别长的情况下,是非常枯燥的。而 的作用就是为了解决这样的麻烦。通过定义Command和Chain的别名,来简化书写。配置文件,可以书写成: <chain name="CommandChain"> <command1 id="1"/> <filter1 id="2"/> <lookupCommand name="chain_command3" optional="true"/> <command2 id="3"/> </chain> <chain name="chain_command3"> <command3 id="3"/> </chain> <command1 name="command4"/>
参考: