在软件开发的 “战场” 上,我们常常会遭遇各种棘手的难题。其中,如何优雅地解耦请求的发送者与接收者,同时实现请求在多个处理者之间灵活、动态地流转,一直是困扰众多开发者的 “拦路虎”。这时,责任链模式如同一位 “救星”,强势登场,为我们打开了高效处理请求的大门。在日常开发中,我们频繁使用的过滤器、拦截器等功能,其背后都离不开责任链模式的强力支撑。然而,很多开发者一听到要自己动手实现一个责任链,就会眉头紧皱,仿佛面对一座难以逾越的高山。
别害怕!今天就给大家带来一款堪称 “神器” 的工具 ——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
<dependency><groupId>commons-chain</groupId><artifactId>commons-chain</artifactId><version>${commons-chain.version}</version></dependency>
2、实现command
这里以 Filter 为例,为大家展示如何实现具体的命令逻辑。下面分别实现了判断飞翔、跳跃和跑步能力的命令,每个命令都有自己独特的功能和逻辑:
public class FlyCommand implements Filter {/*** 执行当前操作** 此方法应由具体的操作实现类来覆盖,以提供具体的操作逻辑* 它负责根据给定的上下文执行业务逻辑,并返回一个布尔值表示执行结果** @param context 执行操作的上下文,包含操作所需的信息和环境设置* @return boolean 表示操作执行的结果,true表示成功,false表示失败* @throws Exception 如果执行过程中发生错误,抛出异常*/@Overridepublic 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;
}}
public class JumpCommand implements Filter {/*** 执行当前操作** 此方法应由具体的操作实现类来覆盖,以提供具体的操作逻辑* 它负责根据给定的上下文执行业务逻辑,并返回一个布尔值表示执行结果** @param context 执行操作的上下文,包含操作所需的信息和环境设置* @return boolean 表示操作执行的结果,true表示成功,false表示失败* @throws Exception 如果执行过程中发生错误,抛出异常*/@Overridepublic 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*/@Overridepublic boolean postprocess(Context context, Exception exception) {System.out.println("判定跳跃能力完毕...");if(exception != null){System.out.println("执行异常:" + exception.getMessage());}return exception == null;}
}
public class RunCommand implements Filter {/*** 执行当前操作** 此方法应由具体的操作实现类来覆盖,以提供具体的操作逻辑* 它负责根据给定的上下文执行业务逻辑,并返回一个布尔值表示执行结果** @param context 执行操作的上下文,包含操作所需的信息和环境设置* @return boolean 表示操作执行的结果,true表示成功,false表示失败* @throws Exception 如果执行过程中发生错误,抛出异常*/@Overridepublic 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*/@Overridepublic boolean postprocess(Context context, Exception exception) {System.out.println("判定跑步能力完毕...");if(exception != null){System.out.println("执行异常:" + exception.getMessage());}return exception == null;}
}
3、将创建的command加入到chain中
@Slf4j
public class ChainTest {private Context data;@Beforepublic void prepareData(){data = new ContextBase();data.put("flyEnabled", "false");data.put("jumpEnabled", "false");data.put("runEnabled", "false");}@Testpublic 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);}}}
运行并查看结果
拥有飞翔的能力:false
拥有跳跃的能力:false
拥有跑步的能力:false
判定跑步能力完毕...
判定跳跃能力完毕...
判定飞翔能力完毕...
a、 如果要根据名字获取command,则可以利用catalog
@Testpublic 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);}}}
运行并查看结果
拥有飞翔的能力:false
拥有跑步的能力:false
拥有跳跃的能力:false
四、与 spring 集成:强强联合,打造无敌组合
1、自定义激活chain注解
@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扫描器
public class ChainClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {public ChainClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {super(registry);}@Overrideprotected 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容器中
public class ChainRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic 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聚合器
@RequiredArgsConstructor
public class CommandDelegete implements Command, InitializingBean {private final ObjectProvider<List<Command>> commandObjectProvider;private final Chain chain = new ChainBase();@Overridepublic boolean execute(Context context) throws Exception {return chain.execute(context);}@Overridepublic void afterPropertiesSet() throws Exception {List<Command> commands = commandObjectProvider.getIfAvailable();if(CollectionUtil.isNotEmpty(commands)){commands.forEach(chain::addCommand);}}
}
5、将chain聚合器注入到spring中
@Configuration
public class CommandAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic CommandDelegete commandDelegete(ObjectProvider<List<Command>> commandObjectProvider){return new CommandDelegete(commandObjectProvider);}}
6、如何使用
@RequiredArgsConstructor
public class UserHandlerInterceptor implements HandlerInterceptor {private final CommandDelegete commandDelegete;@Autowiredprivate ServletContext servletContext;@Overridepublic 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 感兴趣,想要深入了解更多细节,可以访问官网:
https://commons.apache.org/dormant/commons-chain/cookbook.html
demo链接
为了方便大家学习和实践,这里提供了完整的 demo 链接:
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-chain
如果这篇文章对你有所帮助,不妨点赞、转发给身边的小伙伴,让更多的开发者受益于这个强大的工具!同时,也欢迎大家关注我们,获取更多实用的技术干货!