如何在Java中实现动态的责任链?

Tar*_*rek 1 java dynamic chain-of-responsibility

我对业务逻辑验证有一系列要求:

  1. 验证的每个独立步骤必须分开;
  2. 这些步骤的顺序可以由管理员定义;
  3. 可以禁用步骤.
  4. 每个验证步骤不是用户定义的-即,代码编译.

所以我想到实现一个动态的责任链,它从表中加载步骤顺序和类名,用它们实例化它们Class.forName().但是我不太喜欢将className存储在表中,因为这可能会导致潜在的问题(例如,重构验证器的名称只会破坏代码).这就是我做的:

在此输入图像描述

当然,解决方案必须越灵活,它就越复杂.不过,我想知道是否有办法保证上述要求而不将类名存储在表中?

小智 5

你不需要重新发明轮子.您可以使用Apache的Commons Chain作为起点,并与您的自定义解决方案混合使用.它提供了一种优雅而简单的方法来解决您的问题.您还可以将配置保存到XML文件(目录)中,并根据需要从数据库加载它.

这是一个例子:

为了了解Commons Chain的运作方式,让我们从一个有点人为的例子开始:二手车(也就是二手车销售人员)的供应商所采用的业务流程.以下是构成销售流程的步骤:

Get customer information
Test-drive vehicle
Negotiate sale
Arrange financing
Close sale
Run Code Online (Sandbox Code Playgroud)

现在假设您想使用模板方法模式对此流进行建模.您可以创建一个抽象类 - 定义算法 - 看起来像这样:

    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();   
}
Run Code Online (Sandbox Code Playgroud)

现在让我们看看如何使用Commons Chain实现此过程.首先,下载Commons Chain.您可以将最新的每晚下载作为.zip或.tar文件获取,或者您可以通过从CVS或SubVersion源存储库中查看Commons Chain模块来获取最新的代码.解压缩归档文件,将commons-chain.jar文件放在类路径中.

要使用Commons Chain实现业务流程,请将流程中的每个步骤实现为具有名为execute()的单个公共"do all all"方法的类.这是Command模式的传统用法.这是"获取客户信息"步骤的简单实现.

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

出于说明目的,这个类没有太大作用.但是,它会将客户的名称存储在上下文中.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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以将过程定义为序列或"命令链".

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

此示例显示如何使用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>
Run Code Online (Sandbox Code Playgroud)

Chain配置文件可以包含组合在一起的多个链定义.对于此示例,链定义在默认目录中定义.实际上,您可以在此文件中包含多个命名目录,每个目录都有自己的链集.

现在,不是像在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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