Apache的Common-Chain 使用
简介责任模式
责任链模式是一种设计模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。即,在流水线上,属于自己的就处理,不属于自己的就丢给下一个处理。
- 意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
- 主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
- 优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。
- 缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。
- 使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求[0]。
[0] 引用菜鸟教程,具体Demo可以看菜鸟教程
Apache的Common-Chain
Common-Chain是组织复杂处理流“责任链”模式的一种流行技术,正如经典的“Gang of Four”设计模式书中描述的那样。虽然实现这种设计模式所需的基本API契约非常简单,但是有一个基础API有助于使用该模式,并且(更重要的是)鼓励来自多个不同来源的命令实现的组合。它使用“上下文”抽象旨在将命令实现与运行它们的环境隔离开来(例如,可以在Servlet或Portlet中使用的命令,而不必直接绑定到这些环境中的任何一个的API契约)。并最大限度地发挥责任链模式api的效用,基本接口契约以一种除了合适的JDK之外没有依赖关系的方式定义。提供了这些api的便利基类实现,以及web环境(即servlet和portlets)的更专业的实现。
基本的UML
还有个
Catalog
类(顶级接口,用来保存Chains列表,一个Catalog可以有多个Chain组成,可以通过"name"的方式获取当前Catalog中的chain),因为本文暂不使用,所以先不介绍
- Command: 是组成“责任链”的成员,用来执行任务(当其方法返回true时表示自己执行,否则不执行),属于员工,用来找属于的活来做。
- Chain: 用加载和运行command,属于责任中的领导,用来分配工作给员工
- ChainBase: 实现
Chain
接口,提高添加链和执行链的实现。 - Filter: 过滤器,它也属于Command,不过它有
postprocess()
方法,会从末段开始执行所有属于Filter的postprocess方法,属于后勤人员,它有也能用来过滤(得看它放在链的哪个位置)
上下文参数
主要流程
Chain -> Command1,Command2,...(包括Fiter)[->Fiter1,Fiter2...(这里指执行Fiter的postprocess方法)]
案例使用
工具:jdk11、Springboot、gradle、Common-Chain
导包
grade导包
dependencies {
compile('org.springframework.boot:spring-boot-starter-web') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
testCompile('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
// else tool
compile group: 'commons-chain', name: 'commons-chain', version: '1.2'
}
需求
对18岁以上的男性和女性分别显示一段话,对未成年人说一段话
- BaseCommand 添加排序方法
- UserChainService 继承ChinBase来实现排序和Command返回值实现
添加Command
- 在使用前要考虑Spring环境下,添加Command的方式。因为使用IOC来注入我们的
ChainBase
,而我们又不想要显示的添加Command,并且这种添加有且值添加一次(ChainBase
是注入后默认单例),所以这时我们可以考虑使用Spring的InitializingBean
接口来初始化添加Command。
class Xxx implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
xxx 添加Command
}
}
这样在初始化Bean的时候会自动添加Command
代码
DTO
@Data
public class UserDto {
private String id;
private String username;
private Integer age;
private String gender;
}
ChanBase
@Service
@Slf4j
public class UserChainService<T> extends ChainBase {
/**
* 如果有继承BaseCommand则排序
* 无则按数组进行
*
* @param command .
*/
@Override
public void addCommand(Command command) {
super.addCommand(command);
Arrays.sort(commands, (c1, c2) -> {
if (c1 instanceof BaseCommand
&& c2 instanceof BaseCommand) {
return ((BaseCommand) c1).orderCommand() - ((BaseCommand) c2).orderCommand();
}
return 0;
});
}
/**
* 添加过滤,过滤非UserDto的类
*/
private static class UserFilter implements Filter {
@Override
public boolean postprocess(Context context, Exception exception) {
log.warn("方法执行,查看是否是ok!");
return true;
}
/**
* 因为它也为Command,所以false表示通过给下一个执行
*/
@Override
public boolean execute(Context context) throws Exception {
var o = context.get(KeyConstant.CHAIN_KEY);
if (o instanceof UserDto) {
return false;
}
log.warn("不属于UserDto参数,不执行!");
return true;
}
}
/**
* 添加前缀过滤
*/
public UserChainService() {
addCommand(new UserFilter());
log.debug("加入过滤器");
}
public String executeChain(T t) {
if (Objects.isNull(t)) {
return "空值不做任何操作!";
}
var contextBase = new ContextBase();
contextBase.put(KeyConstant.CHAIN_KEY, t);
try {
var isExecute = execute(contextBase);
if (isExecute) {
var msg = (String) contextBase.get(KeyConstant.SERVICE_I_KEY);
return StringUtils.isEmpty(msg) ? "" : msg;
} else {
return "没有可执行的链";
}
} catch (Exception e) {
log.error("出现异常!", e);
return "执行出现异常";
}
}
}
Command->对男性
@Service
@Slf4j
public class UserCommandI implements BaseCommand, InitializingBean {
@Resource
private UserChainService<UserDto> userChainService;
@Override
@SuppressWarnings("unchecked")
public boolean execute(Context context) throws Exception {
var userDto = (UserDto)context.get(KeyConstant.CHAIN_KEY);
if (userDto.getAge() >= 18 && "男".equals(userDto.getGender())){
var msg = "成年男性,需要对自己负责,事业继续努力,加油!";
context.put(KeyConstant.SERVICE_I_KEY, msg);
return true;
}
return false;
}
@Override
public void afterPropertiesSet() throws Exception {
userChainService.addCommand(this);
log.warn("command1 注册到chain");
}
@Override
public int orderCommand() {
return 0;
}
}
Command->对女性
@Service
@Slf4j
public class UserCommandII implements BaseCommand, InitializingBean {
@Resource
private UserChainService<UserDto> userChainService;
@Override
@SuppressWarnings("unchecked")
public boolean execute(Context context) throws Exception {
var userDto = (UserDto) context.get(KeyConstant.CHAIN_KEY);
if (userDto.getAge() >= 18 && "女".equals(userDto.getGender())) {
var msg = "女人何苦为难女人,加油!";
context.put(KeyConstant.SERVICE_I_KEY, msg);
return true;
}
return false;
}
@Override
public void afterPropertiesSet() throws Exception {
userChainService.addCommand(this);
log.warn("command2 注册到chain");
}
@Override
public int orderCommand() {
return 2;
}
}
Command->对未成年人
@Slf4j
public class UserCommandIII implements BaseCommand {
@Resource
private UserChainService<UserDto> userChainService;
/**
* 测试使用init-method
*/
public void init(){
userChainService.addCommand(this);
log.warn("将command3注册的chain当中");
}
@Override
@SuppressWarnings("unchecked")
public boolean execute(Context context) throws Exception {
var userDto = (UserDto)context.get(KeyConstant.CHAIN_KEY);
if (userDto.getAge() <= 18){
var msg = "未成年人,学习加油!";
context.put(KeyConstant.SERVICE_I_KEY, msg);
return true;
}
return false;
}
@Override
public int orderCommand() {
return 1;
}
}
注意:这里未成年人与女性有个年龄等于18时条件相冲的问题,这里用来验证排序
这里使用了另外一种初始化方式 init-method,具体自行百度
Test
@Resource
private UserChainService<UserDto> userChainService;
/**
* 测试Springboot 环境下的common chain 的责任链路
*/
@Test
void rlTest() {
var userDto = new UserDto();
userDto.setId("123");
userDto.setUsername("zzp");
userDto.setAge(18);
userDto.setGender("男");
try {
var msg1 = userChainService.executeChain(userDto);
System.out.println(msg1);
userDto.setGender("女");
var msg2 = userChainService.executeChain(userDto);
System.out.println(msg2);
} catch (Exception e) {
e.printStackTrace();
}
}
结果
设置女性的orderCommand()
方法等于0
排序校验成功!
到此案例结束,有疑问的欢迎评论留言^-^