apache-commons
chain的使用
chain是apache提供的通用方法中的责任链,通过chain可以很优雅的将代码中大量的if else替换;并且可以简化代码,业务实现中,可以更专注的编写业务代码,不需要过多的进行逻辑判断。
背景
最近在开发的项目中,不同的处理类型,需要调用不同的第三方接口;部分类型需要调用十几个第三方接口,并且调用第三方的接口是需要按照一定的顺序进行调用的。
如果在代码中通过if else进行判断,会有很多if else的语句,对于后面的维护以及代码的阅读造成一定的困难。
面对此问题,考虑使用责任链进行解决。
工作原理介绍
责任链中的角色
其中,顶级的角色有三个:Command、Chain、Context
Command和Chain的关系是组合模式(Composite patterrn):Chain由一或多哥Command组成,他自己本身也是一个Command,此设计,让责任链的配置文件可以更灵活(当然,灵活的同时,也会造成维护成本的增加)
Context仅仅是一个存放了名称-值对的集合。接口Context在这里作为一个标记接口:它扩展了java.util.Map但是没有添加任何特殊的行为。于此相反,类ContextBase不仅提供了对Map的实现而且增加了一个特性:属性-域透明。这个特性可以通过使用Map的put和get 方法操作JavaBean的域,当然这些域必须使用标准的getFoo和setFoo方法定义。那些通过JavaBean的“setter”方法设置的值,可以通过对应的域名称,用Map的get方法得到。同样,那些用Map的put方法设置的值可以通过JavaBean的“getter”方法得到。
例如,我们可以创建一个专门的context提供显式的customerName属性支持。
详细使用教程
加载依赖
<dependency>
<groupId>commons-chain</groupId>
<artifactId>commons-chain</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-digester/commons-digester -->
<dependency>
<groupId>commons-digester</groupId>
<artifactId>commons-digester</artifactId>
<version>1.8</version>
</dependency>
配置责任链(因为本人觉得xml的配置看起来更加清晰明了,所以,使用xml的配置方式)
<catalog name="auto-sales">
<chain name="toc-sale">
<command id="CompanyTradingCommond" className="com.yatsenglobal.cloud.chain.commond.CompanyTradingCommand"/>
</chain>
<chain name="tob-sale">
<command id="CompanyTradingCommond" className="com.yatsenglobal.cloud.chain.commond.CompanyTradingCommand"/>
</chain>
</catalog>
设置目录加载程序,加载xml配置文件
@Component
@Slf4j
public class CatalogLoader {
private static final String CONFIG_FILE = "/commond/sale-chain.xml";
private ConfigParser parser;
private Catalog catalog;
@PostConstruct
public void init() {
log.info("开始加载上传销售数据的责任链配置");
parser = new ConfigParser();
try {
parser.parse(this.getClass().getResource(CONFIG_FILE));
} catch (Exception e) {
throw new BusinessException("无法加载目录配置文件!");
}
}
public CatalogLoader() {
}
public Catalog getCatalog() {
catalog = CatalogFactoryBase.getInstance().getCatalog("auto-sales");
return catalog;
}
public Command getCommand(String chain) {
return getCatalog().getCommand(chain);
}
public static void main(String[] args) throws Exception {
CatalogLoader catalogLoader = new CatalogLoader();
Catalog catalog = catalogLoader.getCatalog();
Command command = catalog.getCommand("toc-sale");
SaleContext context = new SaleContext();
command.execute(context);
}
}
扩展责任链的上下文信息(用于责任链中各个链条的信息传递)
@Data
public class SaleContext extends ContextBase {
@ApiModelProperty("上传状态")
private String uploadStatus;
@ApiModelProperty("批次号")
private String code;
@ApiModelProperty("汇总上传列表")
List<KdSaleUploadSummary> sumList;
}
使用模板方法的设计模式进行扩展责任链的节点(使该节点与项目实际使用更加适配)
@Slf4j
public abstract class BaseCommand implements Command {
@Override
public boolean execute(Context context) throws Exception {
if (context instanceof SaleContext) {
SaleContext saleContext = (SaleContext) context;
if (!needExe(saleContext.getUploadStatus())) {
log.debug("此次履行,不需要执行");
return false;
}
try {
doExecute(saleContext);
} catch (Exception e) {
throw new SaleUploadException(getUploadStatus(),e.getMessage());
}
return false;
} else {
log.warn("context中的对象类型不是SaleContext,不处理");
return true;
}
}
/** 业务逻辑具体实现 */
protected abstract void doExecute(SaleContext saleContext) throws Exception;
/** 需要执行上传KD的操作 */
protected abstract boolean needExe(String uploadStatus);
/** 执行失败的上传状态 */
protected abstract String getUploadStatus();
}
实现具体的责任链节点
@Slf4j
public class CompanyTradingCommand extends BaseCommand {
@Override
protected void doExecute(SaleContext saleContext) throws Exception {
log.info("进入公司间交易,批次号={}",saleContext.getCode());
}
@Override
protected boolean needExe(String uploadStatus) {
if (KdDeliveryContant.SaleUploadStatus.NOT_BEGIN.equals(uploadStatus)) {
return true;
}
return false;
}
@Override
protected String getUploadStatus() {
return KdDeliveryContant.SaleUploadStatus.COMPANY_TRADING_FAIL;
}
}
测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class AppTest {
@Resource
private CatalogLoader catalogLoader;
@Test
public void testCreatePurchaseOrder() throws Exception {
Command command = catalogLoader.getCommand("toc-sale");
SaleContext context = new SaleContext();
context.setCode("testCode");
context.setUploadStatus("1");
command.execute(context);
}
}