我有一组关于业务逻辑验证的要求:
- 验证的每个独立步骤都必须分开;
- 这些步骤的顺序可以由管理员定义;
- 可以禁用步骤。
- 每个验证步骤都不是用户定义的 - 即代码是编译的。
所以我想到了实现一个动态的责任链,从表中加载步骤顺序和类名,用 Class.forName()
实例化它们。但我不太喜欢将 className 存储在表中,因为这可能会导致潜在的问题(例如,重构 validator 的名称只会破坏代码)。这就是我所做的:
当然,解决方案越灵活,就越复杂。不过,我想知道是否有一种方法可以在不将类名存储在表中的情况下保证上述要求?
最佳答案
您不需要重新发明轮子。您可以使用 Apache 的 Commons Chain 作为起点,并与您的自定义解决方案混合使用。它提供了一种优雅而简单的方法来解决您的问题。 您还可以将您的配置保存到一个 XML 文件(目录)中,并根据需要从您的数据库中加载它。
这是一个例子:
要了解 Commons Chain 的工作原理,让我们从一个有点做作的例子开始:二手车供应商(又名二手车销售人员)采用的业务流程。以下是构成销售流程的步骤:
Get customer information
Test-drive vehicle
Negotiate sale
Arrange financing
Close sale
现在假设您想要使用模板方法模式对此流程建模。您可以创建一个抽象类——定义算法——看起来像这样:
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 文件放在类路径中。
要使用 Commons Chain 实现业务流程,请将流程中的每个步骤实现为一个类,该类具有一个名为 execute() 的公共(public)“全部执行”方法。这是命令模式的传统用法。下面是“获取客户信息”步骤的简单实现。
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;
}
}
出于说明目的,这个类并没有做太多事情。但是,它确实将客户的姓名存储在 Context 中。 Context 对象提供了命令之间的粘合剂。目前,将 Context 想象成一个哈希表,您可以通过键将值填入或从中提取值。所有后续命令现在都可以访问此数据。 TestDriveVehicle、NegotiateSale 和 ArrangeFinancing 命令类是简单的实现,它们只是打印出命令将执行的操作。
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 实现使用上下文来提取在 GetCustomerInfo 命令中设置的客户姓名。
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);
}
}
此示例展示了如何使用 Commons Chain API 创建和执行一系列命令。 当然,就像现在几乎所有用 Java 编写的新软件一样,Commons Chain 可以通过 XML 文件进行配置。 将此功能应用于“销售车辆”流程,您现在可以在 XML 文件中定义命令序列。 此文件的规范名称是 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>
链配置文件可以包含多个链定义,这些定义组合在一起进入目录。对于此示例,链定义是在默认目录中定义的。事实上,您可以在此文件中有多个命名目录,每个目录都有自己的一组链。
现在,您无需像在 SellVehicleChain 中那样定义命令序列,而是使用 Commons 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);
}
}
Chain 使用 Commons Digester 来读取和解析配置文件。 要使用此功能,您需要将 Commons Digester .jar 文件添加到类路径中。 我用的是1.6版,没有问题。 Digester 依赖于 Commons Collections(我使用的是 3.1 版), Commons Logging(版本 1.0.4)和 Commons BeanUtils 1.7.0。您还需要将这些 .jars 添加到您的类路径中。 将这些 .jar 文件添加到我的类路径后,CatalogLoader 成功编译并运行。输出与其他两个测试生成的输出完全相同。
来源:http://www.onjava.com/pub/a/onjava/2005/03/02/commonchains.html
关于java - 如何在Java中实现动态责任链?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30239261/