Solon v3.8.0

graph - 动态图生成与运行时变更

</> markdown
2026年1月5日 上午10:12:21

在企业级应用中,最头疼的需求莫过于“流程定制化”。不同的租户、不同的业务场景,往往需要在标准流程的基础上增加或减少某些步骤。如果为每个场景都预定义一个配置文件,系统将变得臃肿不堪。

本篇我们将解锁 Solon-Flow 的杀手锏——动态图编排,看看如何利用 GraphSpec.copy() 在运行时完成流程的“乾坤大挪移”。

1、动态性的核心价值

常见的流程引擎的图定义是“静态”的(从 JSON 或 XML 或 YAML 加载后便不再改变)。而 Solon-Flow 允许你在内存中动态构建、复制和修饰图定义。

  • 千人千面:根据租户配置,动态决定是否插入“短信通知”节点。
  • 运行时修正:在不停止服务的情况下,临时调整某个节点的审批逻辑。
  • 热插拔插件:根据数据库中的插件列表,实时拼装业务链路。

2、核心 API:GraphSpec.copy()

GraphSpec.copy(graph) 是实现动态性的核心方法。它能将一个已有的 Graph 实例反转回 GraphSpec 状态,让你在保留原有逻辑的基础上进行增删改。

源码逻辑分析:

public static GraphSpec copy(Graph graph) {
    // 内部通过 toJson 序列化再反序列化,确保得到一个完全独立的规格副本
    return fromText(graph.toJson());
}

3、实战场景一:流程的“动态微调”

假设我们有一个标准的“请假流程”,但针对“高级VIP员工”,我们需要在最后动态增加一个“行政关怀”节点。

public Graph getDynamicFlow(boolean isVip) {
    // 1. 获取标准流程图
    Graph standardGraph = flowEngine.getGraph("leave_flow");
    
    if (!isVip) {
        return standardGraph;
    }

    // 2. 动态克隆并修饰
    GraphSpec spec = GraphSpec.copy(standardGraph);
    
    // 3. 在原有流程中“动刀”
    // 假设原流程最后节点是 'end',我们在 'audit' 之后插入 'care' 节点
    spec.getNode("audit").linkRemove("end").linkAdd("care");
    
    spec.addActivity("care")
        .title("行政关怀")
        .task("@careProcessor")
        .linkAdd("end");

    // 4. 生成新图
    return spec.create();
}

或者使用 Graph.copy (内部基于 GraphSpec.copy),更简化:

public Graph getDynamicFlow(boolean isVip) {
    // 1. 获取标准流程图
    Graph standardGraph = flowEngine.getGraph("leave_flow");

    if (!isVip) {
        return standardGraph;
    }

    // 2. 动态克隆并修饰
    return Graph.copy(standardGraph, spec -> {
        // 3. 在原有流程中“动刀”
        // 假设原流程最后节点是 'end',我们在 'audit' 之后插入 'care' 节点
        spec.getNode("audit").linkRemove("end").linkAdd("care");

        spec.addActivity("care")
                .title("行政关怀")
                .task("@careProcessor")
                .linkAdd("end");
    });
}

4、实战场景二:根据数据库“插件列表”实时拼装

在复杂的 SaaS 系统中,流程往往由多个可选插件组成。我们可以通过代码逻辑,将离散的插件节点拼装成一条完整的线。

public Graph buildPluginFlow(List<PluginDef> plugins) {
    GraphSpec spec = new GraphSpec("plugin_flow", "动态插件流");
    
    NodeSpec lastNode = spec.addStart("start");

    for (int i = 0; i < plugins.size(); i++) {
        PluginDef p = plugins.get(i);
        String nodeId = "p_" + i;
        
        // 动态添加节点
        spec.addActivity(nodeId)
            .title(p.getTitle())
            .task(p.getTaskBeanName());

        // 自动连接上一个节点
        lastNode.linkAdd(nodeId);
        lastNode = spec.getNode(nodeId);
    }

    lastNode.linkAdd("end");
    spec.addEnd("end");

    return spec.create();
}

或者使用 Graph.create (内部基于 GraphSpec),更简化:

public Graph buildPluginFlow(List<PluginDef> plugins) {
    return Graph.create("plugin_flow", "动态插件流", spec -> {
        NodeSpec lastNode = spec.addStart("start");

        for (int i = 0; i < plugins.size(); i++) {
            PluginDef p = plugins.get(i);
            String nodeId = "p_" + i;

            // 动态添加节点
            spec.addActivity(nodeId)
                    .title(p.getTitle())
                    .task(p.getTaskBeanName());

            // 自动连接上一个节点
            lastNode.linkAdd(nodeId);
            lastNode = spec.getNode(nodeId);
        }

        lastNode.linkAdd("end");
        spec.addEnd("end");
    });
}

5. 性能与安全建议

虽然动态生成非常强大,但在生产环境中使用时需注意以下几点:

对象缓存:虽然 GraphSpec.create() 很快,但如果在高并发接口中频繁 copy 和 create,会产生大量碎片对象。建议根据业务 Key(如 tenantId + flowId)将生成的 Graph 对象缓存在本地或 FlowEngine 中。

拓扑校验:动态拼装图时,务必保证流程的闭环。Solon-Flow 会在执行时检查路径完整性,但在编排阶段建议自行校验 Start 和 End 是否存在。

线程安全:GraphSpec 是非线程安全的,建议在局部变量中完成构建后再发布为 Graph 实例。

结语

Solon-Flow 的动态性让开发者从“写死配置”的苦力中解脱出来。通过 GraphSpec.copy() 和编排 API,你可以像操纵普通 Java 对象一样操纵业务逻辑图。

在下一篇 《影子分身 —— 节点的拦截、监听与 AOP》 中,我们将研究如何在不改动图结构的情况下,为这些节点挂载统一的“监控”和“治理”能力。