Commons-chain是apache commons中的一个子项目,主要被使用在"责任链"(执行链)的场景中,struts中action的调用过程,就是使用了"chain"框架做支撑.如果你的项目中,也有基于此种场景的需求,可以考虑使用它.
所谓"责任链"就是:一系列有序的command能够按照顺序执行,并能够互相交换或者传递执行结果;和我们常说的"责任链"模式类似.
commons-chain通过被用在web项目中,或者在基于"规则引擎"的其他模块中,commons-chain本身基于commons-digester作为"规则"的解析引擎,使用起来非常简单.
一.API简介:
1) Command: 顶级接口,一个可执行的"指令",多个command组成"责任链".只有一个方法:
- boolean execute(Context context):当"责任链"开始调用时,将会依次执行链上的所有Command对象的execute方法,直到结束,或者抛出异常,或者某个command返回true终止调用.context对象表示当前"责任链"的上下文信息,它可以用来保存一些临时变量.此方法如果返回false,表示继续责任链将会继续执行后续command,如果返回true,则表示终止调用,后续的command将不会被执行.
2) Chain: 顶级接口,表示组织多个Command,形成一个有序的"责任链",它保持了一个command的集合(数组),Chain接口本身也扩展了Command接口,它的实现类为ChainBase,我们通过会继承它来实现一些自定义的操作.
- void addCommand(Command command):向chain中添加Command节点.责任链中的command顺序将根据其被添加的时间有关.当Chain.execute()被执行时,将会依次执行链中的command.
- boolean execute(Context context): 依次调用当前chain中的所有command,直到结束,或者抛出异常,或者某个command返回true..command抛出的异常将会被捕获,并交给Chain中的Filter处理,如果Filter未能处理,异常将会被重新抛出.需要提醒,Chain中可以包括Filter组件,Filter的执行顺序为倒序.
3) Filter: 顶级接口,"过滤器",它本身也扩展了Command接口,能够和Command一样添加到Chain中,需要注意的是Filter.execute方法,仍然会像其他Command一样被"依次"执行;不过Filter中还提供了一个非常有用的方法:
- boolean postprocess(Context context, Exception exception) : 在Chain执行完Command之后(包括Filter.execute),将会从链的末端倒序依次执行所有的Filter.postprocess()方法,Filter中如果抛出异常将会被忽略;此方法的设计初衷为"清理Context"中参数或者执行结果,或者重置执行结果,如果在Command中存在创建"资源消耗"的对象(比如数据源连接,比如线程资源等),可以在Filter中考虑释放这些资源.当所有的Filter执行完成之后,Context中保持的数据,应该是可靠的.
4) Context:类似于"session",用来保存当前Chain中需要被保持的"变量参数"或者"Command执行的结果";Context内部存储结构为一个Map,它的数据可以被Chain中的Command操作;Context并非为线程安全的,也不期望此实例能够被重用;每次Chain被调用,都应该创建新的Context实例;其实现类为ContextBase.
5) Catalog:顶级接口,用来保存Chains列表,一个Catalog可以有多个Chain组成,可以通过"name"的方式获取当前Catalog中的chain.
二.代码样例:
1) 环境:commons-chain-1.2.jar,commons-digester-1.7.0.jar
2) catalog.xml: 位于/src/main/resources目录,我们可以使用digester引擎来解析此xml文件并获得catalog对象列表:
<?xml version="1.0" ?>
<!-- @see ConfigRuleSet.java -->
<catalog>
<chain name="printChain">
<command name="printDate" className="com.test.chain.PrintDateCommand" />
<command name="printTimestamp" className="com.test.chain.PrintTimestampCommand" />
</chain>
</catalog>
<!-- you can have more catalogs,but everyone should have a unique name -->
<!--
<catalog name="printCatalog">
<chain name="test">
<command name="printDate" className="com.test.chain.PrintDateCommand" />
<command name="printTimestamp" className="com.test.chain.PrintTimestampCommand" />
</chain>
</catalog>
-->
每个catalog元素,都可以指定"name"属性,不过name在全局应该是唯一的,上述xml结构为全限定名的样例.
3) 程序样例:
//ConfigRuleSet中定义了"规则"
ConfigParser configParser = new ConfigParser();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//位于classpath位置
URL url = classLoader.getResource("catalog.xml");
configParser.parse(url);
//获得默认的catalog
//其中CatalogFactoryBase是单例模式,在parse是会触发CatalogFactory的实例化
Catalog catalog = CatalogFactoryBase.getInstance().getCatalog();
Command chain = catalog.getCommand("printChain");
Context context = new ContextBase();//A session map,can be useed by all commands
//当前chain中,所有的command依次执行,直到结束,或者其中一个command返回true,或者抛出异常
chain.execute(context);
三.Chain与servlet
1) web.xml
<context-param>
<param-name>org.apache.commons.chain.CONFIG_CLASS_RESOURCE</param-name>
<!-- 注意自己的classpath配置-->
<!-- 其他param,请参见ChainLisntener类 -->
<param-value>catalog.xml</param-value>
</context-param>
<listener>
<listener-class>org.apache.commons.chain.web.ChainListener</listener-class>
</listener>
2) 获取Catalog:
//从ServletContext中获取Catalog,请注意并发环境下使用
//catalog,chain,command,filter的设计需要考虑到"并发"操作
Catalog catalog = (Catalog) request.getSession().getServletContext().getAttribute("catalog");
Command command = catalog.getCommand("printCatalog");
try{
Context context = new ContextBase();
//context.put("dataSource",dataSource);//
boolean stop = command.execute(context);
if(stop){
//如果某个Command执行异常,你可以尝试将"异常信息"放入context中,以便在此处处理
//Throwable exception = context.get("exception");
//...
}
//你应该把command的执行结果放在context中,以便在此处获取
//Object result = context.get("result");
if(result)
}catch(Exception e){
//please check.
}
--END--