在软件开发的 "战场" 上,我们常常会遭遇各种棘手的难题。其中,如何优雅地解耦请求的发送者与接收者,同时实现请求在多个处理者之间灵活、动态地流转,一直是困扰众多开发者的 "拦路虎"。这时,责任链模式如同一位 "救星",强势登场,为我们打开了高效处理请求的大门。在日常开发中,我们频繁使用的过滤器、拦截器等功能,其背后都离不开责任链模式的强力支撑。然而,很多开发者一听到要自己动手实现一个责任链,就会眉头紧皱,仿佛面对一座难以逾越的高山。
别害怕!今天就给大家带来一款堪称 "神器" 的工具 ------apache commons-chain。有了它,实现责任链将变得轻而易举,就像拥有了一把万能钥匙,能够轻松解锁各种复杂的场景。不管你是经验丰富的开发老手,还是刚刚踏入编程领域的新手小白,这款工具都能让你在实现责任链的过程中事半功倍!
一、commons-chain:责任链实现的 "超级外挂"
commons-chain 是 apache commons 大家族中的重要一员,它就像是一个功能强大的 "超级外挂",专门用于创建和管理命令对象。这些命令对象可不是普通的角色,它们各自拥有独特的使命,就像一个个训练有素的特种兵,随时准备执行各种高难度任务。借助 commons-chain,开发者可以将多个命令以链式的方式巧妙地串联起来,每个命令专注于完成自己的任务,完成后便如同接力赛中的运动员一样,精准地将控制权传递给下一个命令,从而构建出一条高效、灵活的责任链。
二、commons-chain 核心接口:构建责任链的 "秘密武器"
Command 接口: 它是责任链中每一个具体任务的执行者,如同战场上冲锋陷阵的士兵。这个接口只有一个核心方法boolean execute(Context context),别看它简单,却蕴含着巨大的能量。当该方法返回true时,就像是吹响了停止的号角,责任链中的其他命令将停止执行;而返回false时,则意味着责任链可以继续前进,执行后续的命令。不过,一旦执行过程中出现异常,就如同遭遇了突发状况,责任链也会被迫中断,后续命令将无法执行。
Chain 接口: 它是责任链的组织者和管理者,就像是一位经验丰富的指挥官,负责将各个命令有序地编排在一起。所有要在责任链中执行的命令,都需要先添加到这个 Chain 中。而且,它还实现了 Command 接口,这意味着 Chain 不仅能够组织命令,自身也具备执行命令的能力,可谓是 "身兼数职"。
Context 接口: 它是命令执行的上下文环境,如同一个信息共享的 "中央仓库",在整个责任链中传递着关键信息。各个命令可以通过它访问和修改共享数据,实现不同命令之间的协同作战。此外,它还实现了 Map 接口,就像一个灵活多变的百宝箱,能够存储各种类型的信息,方便命令随时取用。
Filter 接口: 它是一种特殊的 Command,拥有更强大的功能,就像是特种兵中的精英。除了继承 Command 的execute方法外,它还新增了一个boolean postprocess(Context context, Exception exception)方法。commons chain 会在执行完 Filter 的execute方法之后,无论责任链以何种方式结束,都会接着执行postprocess方法。而且,execute方法的执行顺序与 Filter 在 Chain 中出现的位置一致,而postprocess方法的执行顺序则与之相反。postprocess方法的返回值根据异常对象来决定,有异常时返回false,否则返回true,这样就能确保在最后能及时抛出异常,方便我们进行错误排查和处理。
Catalog 接口: 它就像是一个命令的 "智能索引",是一个逻辑命名的命令(或 Chain)集合。通过使用它,Command 的调用者无需了解具体实现 Command 的类名,只需通过名字就能像在图书馆中查找书籍一样,轻松获取所需的 Command 实例,大大提高了开发效率。
三、使用示例:手把手教你快速上手
1、项目中的pom引入commons-chain gav
xml
<dependency>
<groupId>commons-chain</groupId>
<artifactId>commons-chain</artifactId>
<version>${commons-chain.version}</version>
</dependency>
2、实现command
这里以 Filter 为例,为大家展示如何实现具体的命令逻辑。下面分别实现了判断飞翔、跳跃和跑步能力的命令,每个命令都有自己独特的功能和逻辑:
java
public class FlyCommand implements Filter {
/**
* 执行当前操作
*
* 此方法应由具体的操作实现类来覆盖,以提供具体的操作逻辑
* 它负责根据给定的上下文执行业务逻辑,并返回一个布尔值表示执行结果
*
* @param context 执行操作的上下文,包含操作所需的信息和环境设置
* @return boolean 表示操作执行的结果,true表示成功,false表示失败
* @throws Exception 如果执行过程中发生错误,抛出异常
*/
@Override
public boolean execute(Context context) throws Exception {
Object fly = context.get("flyEnabled");
boolean flyEnabled = fly != null && "true".equalsIgnoreCase(fly.toString());
System.out.println("拥有飞翔的能力:" + flyEnabled);
return flyEnabled;
}
/**
* 在飞行能力判断完成后进行后处理
*
*
* @param context 上下文环境,可能包含执行判断飞行能力前后的相关环境信息
* @param exception 在判断飞行能力过程中可能发生的异常,如果没有异常,则为null
* @return boolean 表示飞行能力判断是否成功完成如果没有异常,则返回true,否则返回false
*/
@Override
public boolean postprocess(Context context, Exception exception) {
System.out.println("判定飞翔能力完毕...");
if(exception != null){
System.out.println("执行异常:" + exception.getMessage());
}
return exception == null;
}
}
java
public class JumpCommand implements Filter {
/**
* 执行当前操作
*
* 此方法应由具体的操作实现类来覆盖,以提供具体的操作逻辑
* 它负责根据给定的上下文执行业务逻辑,并返回一个布尔值表示执行结果
*
* @param context 执行操作的上下文,包含操作所需的信息和环境设置
* @return boolean 表示操作执行的结果,true表示成功,false表示失败
* @throws Exception 如果执行过程中发生错误,抛出异常
*/
@Override
public boolean execute(Context context) throws Exception {
Object jump = context.get("jumpEnabled");
boolean jumpEnabled = jump != null && "true".equalsIgnoreCase(jump.toString());
System.out.println("拥有跳跃的能力:" + jumpEnabled);
return jumpEnabled;
}
/**
* 在跳跃能力判断完成后进行后处理
*
*
* @param context 上下文环境,可能包含执行判断跳跃能力前后的相关环境信息
* @param exception 在判断跳跃能力过程中可能发生的异常,如果没有异常,则为null
* @return boolean 表示跳跃能力判断是否成功完成如果没有异常,则返回true,否则返回false
*/
@Override
public boolean postprocess(Context context, Exception exception) {
System.out.println("判定跳跃能力完毕...");
if(exception != null){
System.out.println("执行异常:" + exception.getMessage());
}
return exception == null;
}
}
java
public class RunCommand implements Filter {
/**
* 执行当前操作
*
* 此方法应由具体的操作实现类来覆盖,以提供具体的操作逻辑
* 它负责根据给定的上下文执行业务逻辑,并返回一个布尔值表示执行结果
*
* @param context 执行操作的上下文,包含操作所需的信息和环境设置
* @return boolean 表示操作执行的结果,true表示成功,false表示失败
* @throws Exception 如果执行过程中发生错误,抛出异常
*/
@Override
public boolean execute(Context context) throws Exception {
Object run = context.get("runEnabled");
boolean runEnabled = run != null && "true".equalsIgnoreCase(run.toString());
System.out.println("拥有跑步的能力:" + runEnabled);
return runEnabled;
}
/**
* 在跑步能力判断完成后进行后处理
*
*
* @param context 上下文环境,可能包含执行判断跑步能力前后的相关环境信息
* @param exception 在判断跑步能力过程中可能发生的异常,如果没有异常,则为null
* @return boolean 表示跑步能力判断是否成功完成如果没有异常,则返回true,否则返回false
*/
@Override
public boolean postprocess(Context context, Exception exception) {
System.out.println("判定跑步能力完毕...");
if(exception != null){
System.out.println("执行异常:" + exception.getMessage());
}
return exception == null;
}
}
3、将创建的command加入到chain中
java
@Slf4j
public class ChainTest {
private Context data;
@Before
public void prepareData(){
data = new ContextBase();
data.put("flyEnabled", "false");
data.put("jumpEnabled", "false");
data.put("runEnabled", "false");
}
@Test
public void testChain(){
Chain chain = new ChainBase();
chain.addCommand(new FlyCommand());
chain.addCommand(new JumpCommand());
chain.addCommand(new RunCommand());
try {
chain.execute(data);
} catch (Exception e) {
log.error("执行链路失败",e);
}
}
}
运行并查看结果
java
拥有飞翔的能力:false
拥有跳跃的能力:false
拥有跑步的能力:false
判定跑步能力完毕...
判定跳跃能力完毕...
判定飞翔能力完毕...
a、 如果要根据名字获取command,则可以利用catalog
java
@Test
public void testCatalog(){
Catalog catalog = new CatalogBase();
catalog.addCommand("fly", new FlyCommand());
catalog.addCommand("jump", new JumpCommand());
catalog.addCommand("run", new RunCommand());
Iterator names = catalog.getNames();
while (names.hasNext()) {
try {
String name = (String) names.next();
catalog.getCommand(name).execute(data);
} catch (Exception e) {
log.error("执行链路失败",e);
}
}
}
运行并查看结果
java
拥有飞翔的能力:false
拥有跑步的能力:false
拥有跳跃的能力:false
四、与 spring 集成:强强联合,打造无敌组合
1、自定义激活chain注解
java
@Target({java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ChainRegistrar.class)
public @interface EnableChain {
/**
* Base packages to scan for annotated components.
* attribute.
*/
String[] basePackages() default {};
}
2、自定义chain扫描器
java
public class ChainClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
public ChainClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
super(registry);
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
String[] interfaceNames = metadata.getInterfaceNames();
return interfaceNames.length > 0
&& Arrays.stream(interfaceNames)
.anyMatch(interfaceName ->
Command.class.getName().equals(interfaceName)
|| Filter.class.getName().equals(interfaceName));
}
}
3、将扫描的command注册到spring容器中
java
public class ChainRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableChain.class.getName());
String[] basePackages = (String[]) annotationAttributes.get("basePackages");
System.out.println("basePackages:" + Arrays.toString(basePackages));
ChainClassPathBeanDefinitionScanner chainClassPathBeanDefinitionScanner = new ChainClassPathBeanDefinitionScanner(registry);
chainClassPathBeanDefinitionScanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
chainClassPathBeanDefinitionScanner.scan(basePackages);
}
}
4、编写一个chain聚合器
java
@RequiredArgsConstructor
public class CommandDelegete implements Command, InitializingBean {
private final ObjectProvider<List<Command>> commandObjectProvider;
private final Chain chain = new ChainBase();
@Override
public boolean execute(Context context) throws Exception {
return chain.execute(context);
}
@Override
public void afterPropertiesSet() throws Exception {
List<Command> commands = commandObjectProvider.getIfAvailable();
if(CollectionUtil.isNotEmpty(commands)){
commands.forEach(chain::addCommand);
}
}
}
5、将chain聚合器注入到spring中
java
@Configuration
public class CommandAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CommandDelegete commandDelegete(ObjectProvider<List<Command>> commandObjectProvider){
return new CommandDelegete(commandObjectProvider);
}
}
6、如何使用
java
@RequiredArgsConstructor
public class UserHandlerInterceptor implements HandlerInterceptor {
private final CommandDelegete commandDelegete;
@Autowired
private ServletContext servletContext;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
Context context = new ServletWebContext(servletContext, request, response);
commandDelegete.execute(context);
return true;
}
}
总结
commons-chain 就像是一位贴心的开发伙伴,在实现责任链的道路上,它能够为我们节省大量的时间和精力,让我们的开发工作变得更加轻松高效。尽管它已经进入维护期,但其中蕴含的设计思想和编程技巧依然值得我们深入学习和借鉴。如果你对 commons-chain 感兴趣,想要深入了解更多细节,可以访问官网: commons.apache.org/dormant/com...
demo链接
为了方便大家学习和实践,这里提供了完整的 demo 链接: github.com/lyb-geek/sp... 如果这篇文章对你有所帮助,不妨点赞、转发给身边的小伙伴,让更多的开发者受益于这个强大的工具!同时,也欢迎大家关注我们,获取更多实用的技术干货!