干货来袭!5 分钟学会快速实现责任链,效率直接拉满!

在软件开发的 "战场" 上,我们常常会遭遇各种棘手的难题。其中,如何优雅地解耦请求的发送者与接收者,同时实现请求在多个处理者之间灵活、动态地流转,一直是困扰众多开发者的 "拦路虎"。这时,责任链模式如同一位 "救星",强势登场,为我们打开了高效处理请求的大门。在日常开发中,我们频繁使用的过滤器、拦截器等功能,其背后都离不开责任链模式的强力支撑。然而,很多开发者一听到要自己动手实现一个责任链,就会眉头紧皱,仿佛面对一座难以逾越的高山。

别害怕!今天就给大家带来一款堪称 "神器" 的工具 ------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... 如果这篇文章对你有所帮助,不妨点赞、转发给身边的小伙伴,让更多的开发者受益于这个强大的工具!同时,也欢迎大家关注我们,获取更多实用的技术干货!

相关推荐
就是帅我不改7 分钟前
告别996!高可用低耦合架构揭秘:SpringBoot + RabbitMQ 让订单系统不再崩
java·后端·面试
hhzz20 分钟前
Maven项目中settings.xml终极优化指南
java·jdk·maven
hqxstudying44 分钟前
MyBatis 和 MyBatis-Plus对比
java·数据库·mysql·mybatis
源码哥_博纳软云1 小时前
JAVA国际版多商户运营版商城系统源码多商户社交电商系统源码支持Android+IOS+H5
android·java·ios·微信·微信小程序·小程序·uni-app
猿java1 小时前
为什么复杂的架构一定要做分层设计?
java·面试·架构
whitepure1 小时前
万字详解常用数据结构(Java版)
java·数据结构·后端
天天摸鱼的java工程师1 小时前
你们公司的 QPS 是怎么统计出来的?这 5 种常见方法我踩过一半的坑
java·后端·面试
whitepure1 小时前
万字详解常用算法(Java版)
java·后端·算法
莹莹啦1 小时前
聊一聊Java定时任务框架选型
java·后端
MrSYJ1 小时前
AuthorizationFilter过滤器的功能
java·spring boot·后端