跳到内容

Chain Starter

受众:开发者 摘要:责任链 / 规则链 Facade。Handler 显式 proceed 才继续,适合风控、审批、策略分流、内容审核等“当前规则决定是否传递”的同步链路。

spring-open-starter-chain 是钩子 / 管道 / 责任链三件套里的条件决策层。它依赖 Execution StarterExecutionContext,但不依赖 Hook 或 Pipeline。

何时使用

场景建议
规则自己决定是否继续传递用 Chain
风控、审批、内容审核、策略分流等可中断规则链用 Chain
不确定该用 Hook、Pipeline 还是 Chain先看 钩子 / 管道 / 责任链 专题

钩子 / 管道 / 责任链的选型、推荐场景、注解糖和组合使用见 钩子 / 管道 / 责任链 专题

快速开始

yaml
spring:
  open:
    chain:
      enabled: true
      trace-enabled: true

复制模板:Context

简单场景直接使用 DefaultExecutionContext

java
DefaultExecutionContext context = new DefaultExecutionContext("trace-001");
context.put("amount", 100L);
context.put("currency", "CNY");

正式业务建议定义强类型上下文,把类名和字段改成你的业务名即可:

java
public class OrderRiskChainContext extends DefaultExecutionContext {
    private final Long userId;
    private final Long amount;

    public OrderRiskChainContext(String traceId, Long userId, Long amount) {
        super(traceId);
        this.userId = userId;
        this.amount = amount;
    }

    public Long getUserId() {
        return userId;
    }

    public Long getAmount() {
        return amount;
    }
}

复制模板:注解 Rule 类

推荐用 @Chain.Rule 标注 Handler 类。Handler 只有显式调用 chain.proceed(context) 才会继续传递;少写这行就表示当前规则停止链路。

java
@Component
@Chain.Rule(
        value = "mall.order.risk",
        code = "amount-risk",
        order = 10,
        description = "Check high order amount risk.")
public class OrderAmountRiskRule implements ChainHandler<OrderRiskChainContext> {

    @Override
    public void handle(OrderRiskChainContext context, ChainInvocation<OrderRiskChainContext> chain) {
        if (context.getAmount() > 10_000L) {
            context.terminate("high amount requires manual review");
            return;
        }

        chain.proceed(context);
    }
}

@Chain.Rule 标在 Handler 类上后,可以省掉 getChain()getCode()getDescription()order() 样板;配置里的 chains.<name>.handlers.<code>.order 仍然可以覆盖注解排序。

复制模板:普通 Bean 方法

简单规则也可以直接写在任意 Spring Bean 方法上,不需要单独创建 Handler 类:

java
@Component
public class OrderRiskRules {

    @Chain.Action(value = "mall.order.risk", code = "write-log", order = 10)
    public void writeLog(OrderRiskChainContext context) {
        log.info("risk check traceId={}", context.getTraceId());
    }

    @Chain.Rule(value = "mall.order.risk", code = "amount-risk", order = 20)
    public void amountRisk(OrderRiskChainContext context, ChainInvocation<OrderRiskChainContext> chain) {
        if (context.getAmount() > 10_000L) {
            context.terminate("high amount requires manual review");
            return;
        }
        chain.proceed(context);
    }
}
注解方法签名语义
@Chain.Rulevoid method(C context, ChainInvocation<C> chain)当前规则自己决定是否调用 chain.proceed(context)
@Chain.Actionvoid method(C context)只执行副作用,完成后自动调用 chain.proceed(context)

复制模板:Service 调用

显式 Facade 调用最清楚,适合主流程里需要读取执行结果、自行决定后续动作的场景:

java
public class OrderService {
    public void createOrder(Long userId, Long amount) {
        OrderRiskChainContext context =
                new OrderRiskChainContext("trace-" + userId, userId, amount);

        ChainExecutionResult<OrderRiskChainContext> result =
                Chain.execute("mall.order.risk", context);

        if (result.isInterrupted()) {
            throw new IllegalStateException(result.getMessage());
        }

        // Chain 只做规则传递和中断决策;创建订单、扣库存、创建支付单仍由 Service 控制。
    }
}

复制模板:自动触发业务方法

如果某个业务方法固定需要先跑同一条 Chain,可以直接标 @Chain.Execute。方法参数里必须有一个 ExecutionContext;Chain 完整传递或被配置禁用后才会进入目标方法,中断会抛出 ChainInterruptedException 并阻断目标方法。

@Chain.Execute 走 Spring AOP 代理;请从其他 Spring Bean 调用该方法,同一个类内部 this.createOrder(...) 自调用不会触发注解。

java
@Service
public class OrderService {

    @Chain.Execute("mall.order.risk")
    public Order createOrder(OrderRiskChainContext context) {
        // 能执行到这里,表示 mall.order.risk Chain 已经完整传递。
        return doCreateOrder(context.getUserId(), context.getAmount());
    }
}

上下文参数不在第一个位置时,用 contextArg 指定下标:

java
@Service
public class OrderService {

    @Chain.Execute(value = "mall.order.risk", contextArg = 1)
    public Order createOrder(String requestSource, OrderRiskChainContext context) {
        return doCreateOrder(context.getUserId(), context.getAmount());
    }
}

Inline Rule

一次性规则不需要单独创建 ChainHandler 类,可直接通过 Facade 传 lambda:

java
DefaultExecutionContext context = new DefaultExecutionContext("trace-001")
        .put("amount", 100L);

Chain.execute("mall.order.risk", context,
        Chain.action("print", ctx -> log.info("chain {}", ctx.getTraceId())),
        Chain.rule("amount-risk", (ctx, chain) -> {
            Long amount = ctx.get("amount");
            if (amount != null && amount > 10_000L) {
                ctx.terminate("high amount requires manual review");
                return;
            }
            chain.proceed(ctx);
        }));
API说明
@Chain.Execute标注业务方法,方法执行前自动跑指定 Chain;中断或失败会阻断目标方法
@Chain.Rule标注 Handler 类或普通 Bean 方法,注册正式可配置规则
@Chain.Action标注普通 Bean 方法,注册自动 proceed 的动作规则
Chain.action(code, action)执行 void lambda,执行完成后自动 proceed
Chain.rule(code, callback)拥有完整传递控制权,可自行决定是否调用 chain.proceed(ctx)
.when(predicate)当前上下文满足条件时才执行该 inline rule
.named(name) / .described(description)覆盖 trace 中展示的名称和说明

Inline rule 默认按传参顺序执行;如果配置了 chains.<name>.handlers.<code>.order,仍可通过配置覆盖顺序。正式、可复用、需要独立测试或配置启停的规则仍建议沉淀为 ChainHandler Bean。

Starter 入口与源码骨架

Chain starter 采用 Facade -> Manager -> Registry / Executor -> Handler 的薄分层。业务代码默认使用 Chain.execute(...);固定前置规则链可用 @Chain.Execute 自动触发;正式规则可用 @Chain.Rule / @Chain.Action 注册;简单一次性规则可通过 Chain.rule(...) / Chain.action(...) 传入 inline lambda;复杂装配、测试替换或按名称注入时使用 ChainFactory 创建 ChainFlow,再调用 flow.execute(ctx)

职责
FacadeChain业务调用入口:Chain.execute(name, context);支持 @Chain.Execute 自动触发、@Chain.Rule / @Chain.Action 注册规则、正式 Handler 和 Chain.rule(...) / Chain.action(...) inline lambda
FlowChainFactory / ChainFlow可注入实例入口:chainFactory.flow(name).execute(context)
ManagerDefaultChainManager校验 Chain 名称和上下文,处理 Chain 级启停,委托 Registry / Executor,并在最终结果处通知监听器
RegistryDefaultChainHandlerRegistry按 Chain 名绑定 Handler,应用 Handler 启停配置,并按配置 order、注解 order、ChainHandler.order()、Spring Ordered / @Order 排序
ExecutorDefaultChainExecutor同步执行 Handler;supports=false 自动跳过;不调用 proceed 或上下文终止时返回中断结果;异常默认向调用方抛出
HandlerChainHandler<C>单个规则;需要继续时必须调用 chain.proceed(context)
InvocationChainInvocation<C>Handler 内部继续传递契约,只在规则实现里使用
ListenerChainExecutionListener承接审计、指标、执行日志和运维观测;监听器异常只记录 warning,不影响主流程
ContextExecutionContext / DefaultExecutionContext / ExecutionKey<T>同一条链路内共享 trace、attributes、类型安全属性和中止状态

关键机制顺序:

  1. Chain.execute(name, context) 调用 ChainManager
  2. DefaultChainManager 校验参数和启停配置,向 Registry 取当前 Chain 的 Handler。
  3. DefaultChainHandlerRegistry 完成绑定、启停过滤和排序。
  4. DefaultChainExecutor 按顺序进入 Handler。
  5. supports=false 记为 skipped,并自动继续后续 Handler。
  6. Handler 调用 chain.proceed(context) 才继续;不调用即返回 INTERRUPTED,并写入 context.terminate(...)
  7. context.terminate(reason) 会中断当前 Chain。
  8. Handler 异常默认向调用方抛出,Manager 会先通知监听器收到 FAILED 快照。

Handler 语义

开发者通常复制上文的 @Chain.Rule Handler 类或普通 Bean 方法模板即可。需要理解 Handler 契约时,看下面这些核心方法:

方法说明
getChain()精确 Chain 名,例如 mall.order.risk;使用 @Chain.Rule 时可省略实现
getCode()稳定 handler code,用于配置、trace 和运维展示;使用 @Chain.Rule(code = "...") 时可省略实现
getName()展示名称
getDescription()职责说明
getIdentity()执行身份;中断和失败来源优先展示该值
order()默认排序,未设置时等同 Spring Ordered.LOWEST_PRECEDENCE
supports(context)运行期条件判断;返回 false 时跳过并继续后续 Handler
handle(context, chain)当前规则主体;需要继续时必须调用 chain.proceed(context);不调用会中断链路并写入上下文中止状态

排序与启停

排序来源按优先级生效:

来源示例优先级
配置覆盖chains.mall.order.risk.handlers.amount-risk.order: 10最高
注解 / Handler 默认排序@Chain.Rule(order = 10) / ChainHandler.order()
Spring 排序Ordered / @Order
默认值Ordered.LOWEST_PRECEDENCE最低

配置示例:

yaml
spring:
  open:
    chain:
      chains:
        mall.order.risk:
          enabled: true
          handlers:
            amount-risk:
              enabled: true
              order: 10
            inventory-risk:
              enabled: true
              order: 20

实例入口

需要按名称注入、方便测试替换或与 Hook / Pipeline 组合时,使用 ChainFactory

java
@RequiredArgsConstructor
public class OrderRiskService {

    private final ChainFactory chainFactory;

    public void check(OrderRiskChainContext context) {
        ChainFlow<OrderRiskChainContext> flow = chainFactory.flow("mall.order.risk");
        flow.execute(context);
    }
}

执行结果与监听器

ChainExecutionResult 包含 chain、traceId、status、interruptedBy、message、exception、elapsedMillis 和 ChainHandlerTrace 列表。

状态说明
SUCCESS所有可执行 Handler 都继续传递并完成
INTERRUPTEDHandler 未调用 proceed,或上下文被 terminate
FAILEDHandler 抛出异常;默认仍会向调用方抛出原始异常
DISABLEDChain 被配置禁用

监听器示例:

java
@Component
public class ChainMetricsListener implements ChainExecutionListener {
    @Override
    public void onExecution(ChainExecutionResult<?> result) {
        meterRegistry.counter("spring_open_chain_total",
                "chain", result.getChain(),
                "status", result.getStatus().name()).increment();
    }
}

阶段监听器全部为默认空方法,按需覆盖。监听器自身异常会被隔离并记录 warning,不影响 Chain 主流程。

边界

Chain 是进程内、同步、可中断的规则链。Handler 必须显式调用 chain.proceed(context) 才继续;不适合固定步骤流水线、异步任务、事件广播、持久化工作流或分布式事务。

命名约定

  • Chain 名使用点分层:mall.order.risk / forum.topic.publish.risk
  • Handler code 使用短横线:amount-risk / content-safety / manual-review
  • Handler 类名以规则动作结尾:OrderAmountRiskHandler / ContentSafetyChainHandler
  • Handler 内只做单条规则;复杂固定步骤拆到 Pipeline

验证命令

bash
./mvnw -pl spring-open-starters/spring-open-starter-chain -am test -DskipITs -DskipFrontend
git diff --check

Released under the Apache License 2.0.