Dynamic Chain of Responsibility in Spring: A Practical Implementation

This article explains how to split a multi‑function OpenAPI endpoint into independent steps using the responsibility‑chain pattern in Spring, detailing the abstract component, context sharing, bean retrieval via ApplicationContext, and a controller that dynamically orders execution through a query parameter, with full code examples.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Dynamic Chain of Responsibility in Spring: A Practical Implementation

Background

In a project an OpenAPI endpoint provides many functions (parameter validation, configuration checks, data persistence, messaging, etc.) and different customers require different subsets of these functions.

Design Idea

To make each function independent, the responsibility‑chain pattern is used. An abstract class ComponentAbstract defines handlerRequest that measures execution time and delegates to an abstract doHandler. Each concrete component extends this class and is annotated with @Component("n") to give a custom name.

A shared context class Contxt carries data between components. The Spring ApplicationContext is used to retrieve component beans by the name defined in the annotation, allowing the execution order to be defined at runtime (e.g., via a query parameter).

Key Classes

ComponentAbstract.java

– base class for chain nodes, includes timing logic. Contxt.java – Lombok‑annotated POJO with fields name, age, adrss, userid. AopProxyUtils.java – implements ApplicationContextAware to expose static methods for obtaining beans (by class or name). LiteFlowController.java – REST controller exposing /test/chain. It parses the index parameter, splits it into component names, retrieves each bean via AopProxyUtils.getProxyBean, and invokes handlerRequest on the shared context.

Code Samples

Maven dependencies (FastJSON, Lombok):

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>

Abstract component:

import com.liran.middle.liteflow.slot.Contxt;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;

@Slf4j
public abstract class ComponentAbstract {
    public void handlerRequest(Contxt contxt) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        this.doHandler(contxt);
        stopWatch.stop();
        long cost = stopWatch.getTotalTimeMillis();
        if (cost <= 10) {
            log.info("-----------Execution {} took excellent: {} ms-----------", getClass(), cost);
        } else if (cost <= 50) {
            log.info("-----------Execution {} took normal: {} ms-----------", getClass(), cost);
        } else if (cost <= 500) {
            log.info("-----------Execution {} took delayed: {} ms-----------", getClass(), cost);
        } else if (cost <= 1000) {
            log.info("-----------Execution {} took slow: {} ms-----------", getClass(), cost);
        } else {
            log.info("-----------Execution {} took stuck: {} ms-----------", getClass(), cost);
        }
    }
    public abstract void doHandler(Contxt contxt);
}

Example concrete component (Test1):

@Component("1")
@Slf4j
public class Test1 extends ComponentAbstract {
    @Override
    public void doHandler(Contxt contxt) {
        log.info("Test1‑order1‑context: {}", JSON.toJSONString(contxt));
        contxt.setName("Test1");
        contxt.setAge("Test1");
        contxt.setAdrss("Test1");
        contxt.setUserid("Test1");
    }
}

Controller that drives the chain:

@RestController
@RequestMapping("/test")
@Slf4j
public class LiteFlowController {
    @GetMapping("chain")
    public String pattern(@RequestParam String index) {
        Contxt contxt = Contxt.builder()
                .age("init").adrss("init").name("init").userid("init").build();
        String[] split = index.split(",");
        for (String className : split) {
            ComponentAbstract handler = (ComponentAbstract) AopProxyUtils.getProxyBean(className);
            if (ObjectUtils.isNotEmpty(handler)) {
                handler.handlerRequest(contxt);
            } else {
                log.info("Component not found: {}", className);
            }
        }
        return JSON.toJSONString(contxt);
    }
}

Notes

The same functionality could be achieved more easily with the LiteFlow framework, which offers friendly documentation and powerful features.

Reference implementation repository: https://gitee.com/bryan31/liteflow-example

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Chain of ResponsibilityJavaaopspringdesign patternLiteFlowrest
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.