Flowable22变量监听器---------------持续更新中
变量监听器(Variable Listener)。这个功能并非 BPMN 2.0 的标准规范,而是 Flowable 引擎提供的一个强大扩展,专门用于响应流程变量的变化。
可以把它理解为流程中的一个 “后台数据哨兵” 或 “数据变更审计员”。它在后台默默工作,一旦发现某个(或某些)流程变量被创建、更新或删除,它就会立即执行预定义的逻辑。
变量监听器的核心是 “事件驱动的后台动作”。它与条件事件最大的不同在于:
变量监听器不影响流程的路径。 它不会使流程暂停、继续或跳转。
它的主要目的是执行一个“副作用”(Side Effect),比如数据计算、日志记录、调用外部API等。
工作机制:
你可以将一个或多个变量监听器附加在整个流程上,或者附加在某个具体的活动节点(如用户任务)上。当流程执行过程中,变量发生变化时,Flowable 引擎会检查是否有匹配的监听器,并触发其内部逻辑。
关键属性与配置
在配置变量监听器时,有几个关键的属性:
flowable:event (必需): 指定监听哪种变量事件。
create:仅在变量首次被创建时触发。
update:仅在变量的值被更新时触发。
delete:仅在变量被删除时触发。
all:以上三种事件任意一种发生时都触发(不推荐,除非确实需要,否则会产生不必要的性能开销)。
flowable:variableName (可选): 指定要监听的具体变量名。如果不提供,它会监听所有变量的变化。强烈建议指定具体的变量名,以提高效率和精确性。
执行逻辑 (必需): 指定触发时要执行的代码。有四种实现方式,和执行监听器(Execution Listener)类似:
flowable:class: 执行一个实现了 org.flowable.engine.delegate.VariableListener 接口的 Java 类。
flowable:expression: 执行一个 JUEL 表达式,通常用于调用一个 Spring Bean 的方法。
flowable:delegateExpression: 和 expression 类似,但更推荐用于 Spring 环境,它也用于调用 Spring Bean。
flowable:script: 直接在 BPMN 文件中内嵌一段脚本(如 Groovy, JavaScript)。
变量监听器定义在 标签内。
a. 附加在整个流程上 (Process Level)
<process id="myProcess" name="My Process"><extensionElements><!-- 当变量 'status' 被更新时,调用一个Java类 --><flowable:variableListener flowable:variableName="status" flowable:event="update" flowable:class="com.example.MyStatusUpdateListener" /><!-- 当任何变量被创建时,执行一个脚本 --><flowable:variableListener flowable:event="create" flowable:scriptFormat="groovy"><flowable:script><![CDATA[// 记录日志或做其他事println("新变量被创建: " + variableName + " = " + value);]]></flowable:script></flowable:variableListener></extensionElements><!-- ... 流程的其他部分 ... -->
</process>
b. 附加在具体活动上 (Activity Level)
<userTask id="myUserTask" name="My Task"><extensionElements><!-- 仅在该任务激活期间,当变量 'itemPrice' 更新时,调用Spring Bean --><flowable:variableListener flowable:variableName="itemPrice" flowable:event="update" flowable:delegateExpression="${priceCalculatorBean}" /></extensionElements>
</userTask>
使用场景(什么时候用?)
变量监听器非常适合处理那些与流程流转无直接关系、但又需要根据数据变化来触发的后台任务。
实时数据聚合与计算:
场景: 在一个报销流程中,用户在一个表单里逐条添加报销项(如交通、餐饮)。每添加一条(即创建一个 item_xxx 变量),变量监听器就自动触发,实时计算总金额并更新 totalAmount 变量,用户在界面上能立刻看到总价变化。
详细的审计与日志记录:
场景: 在一个高风险的审批流程中,需要记录每次审批意见(一个变量)的变更历史。可以设置一个监听 approvalComment 变量 update 事件的监听器,将每次变更的旧值、新值、操作人、时间点记录到独立的审计表中。
数据校验与预警(非阻塞式):
场景: 用户填写一个表单,输入了一个折扣率 discountRate。一个变量监听器可以立刻检查这个值是否超过了某个阈值(比如30%)。如果超过,它不阻塞流程,而是可以发送一个邮件通知给风控部门,或者在流程中设置一个“风险预警”的标志变量。
与外部系统轻量级同步:
场景: 当订单流程中的 orderStatus 变量更新为 SHIPPED 时,一个变量监听器可以被触发,调用一个简单的外部API,通知物流系统订单已发货。
动态报价与订单配置流程
想象一个销售为客户配置复杂产品的场景,比如定制电脑、企业软件套餐或工业设备。价格不是固定的,而是根据所选的组件和应用的折扣动态变化的。
痛点:
销售人员每选择一个组件,都需要手动重新计算总价,效率低下且容易出错。
如果应用了某个特殊的折扣码,可能需要触发一个经理审批流程,但这个判断不应该在销售人员完成所有配置之后才进行,最好能实时给予提示。
使用变量监听器的优化方案:
我们将在流程中设置变量监听器,来“监视”订单组件和折扣码的变化。一旦这些变量发生改变,监听器就会在后台自动、实时地重新计算总价,并检查折扣是否超规。整个过程对销售人员是无感的,他们只会在界面上看到总价的实时刷新。
流程步骤详解
创建报价单 (Create Quote)
销售代表为客户启动一个新的报价流程。
配置订单 (Configure Order)
这是一个核心的用户任务。销售代表在这个任务对应的界面上进行操作:
添加/移除产品组件:这个操作会更新一个名为 items 的流程变量(可能是一个JSON数组或Map)。
输入折扣码:这个操作会更新一个名为 discountCode 的流程变量。
这是变量监听器发挥作用的地方! 我们将监听器附加在整个流程上,这样无论何时 items 或 discountCode 变量发生变化,它都会被触发。
变量监听器逻辑(后台自动执行)
监听 items 变量的变化:
事件: create, update
动作: 触发价格计算逻辑。重新计算小计金额 (subtotal),然后基于当前的折扣码,重新计算最终总价 (totalAmount),并更新这两个流程变量。
监听 discountCode 变量的变化:
事件: create, update
动作: 同样触发价格计算逻辑。并且,它还会检查折扣码对应的折扣率。如果折扣率超过20%,就设置一个标志位变量 approvalNeeded = true;否则设置为 false。
确认订单 (Confirm Order)
当销售代表完成所有配置后,点击“确认订单”按钮,完成“配置订单”这个用户任务。
需要经理审批吗? (Approval Needed?)
这是一个排他网关。它检查由变量监听器设置的 approvalNeeded 变量。
路径1 (No): 如果 ${approvalNeeded == false},说明是常规订单,直接跳到“生成正式订单”步骤。
路径2 (Yes): 如果 ${approvalNeeded == true},说明折扣超规,流程需要转给经理审批。
经理审批 (Manager Approval)
销售经理收到审批任务,决定是批准还是拒绝这个高折扣订单。
生成正式订单 (Generate Official Order)
这是一个服务任务(Service Task),系统在后台生成最终的、不可修改的订单PDF,并存入CRM系统。
订单完成 / 订单关闭
流程正常结束或在经理拒绝后结束。
变量监听器在此场景中的价值
实时反馈与卓越体验:销售人员在界面上操作时,总价实时更新,无需手动计算或等待提交。如果折扣超规,可以立即看到提示,而这一切都是由后台监听器驱动的。
逻辑解耦:核心的业务流程(配置 -> 审批 -> 生成订单)保持简洁清晰。而复杂、频繁的价格计算和规则校验逻辑,被封装在监听器中,与主流程解耦。
效率和准确性:自动计算避免了人工错误,大大提高了报价效率。
它与脚本任务的区别:如果把价格计算放在“配置订单”任务之后的一个脚本任务里,那么只有在销售点击“确认订单”后才能计算。而变量监听器是在用户任务“正在进行时”实时触发的,这是本质区别。
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:flowable="http://flowable.org/bpmn" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef"><process id="dynamicQuoteProcess" name="动态报价订单流程" isExecutable="true"><extensionElements><!-- 这是核心:定义在流程级别的变量监听器 --><flowable:variableListener flowable:variableName="items" flowable:event="create,update" flowable:scriptFormat="groovy"><flowable:script><![CDATA[// 模拟价格计算逻辑def subtotal = 0.0;def itemsMap = execution.getVariable("items"); // 假设 items 是一个 Map, e.g., ["itemA": 2, "itemB": 1]if (itemsMap != null) {// 实际场景中会从数据库查询价格if (itemsMap.containsKey("itemA")) subtotal += itemsMap.get("itemA") * 100.0;if (itemsMap.containsKey("itemB")) subtotal += itemsMap.get("itemB") * 250.0;}execution.setVariable("subtotal", subtotal);// 触发总价计算(调用与 discountCode 监听器相同的逻辑)// 为简化,直接在这里也计算总价def discountRate = execution.getVariable("discountRate") ?: 0.0;def totalAmount = subtotal * (1 - discountRate);execution.setVariable("totalAmount", totalAmount);]]></flowable:script></flowable:variableListener><flowable:variableListener flowable:variableName="discountCode" flowable:event="create,update" flowable:scriptFormat="groovy"><flowable:script><![CDATA[// 模拟折扣码解析逻辑def discountCode = execution.getVariable("discountCode");def discountRate = 0.0;if ("SAVE25".equals(discountCode)) {discountRate = 0.25;} else if ("SAVE10".equals(discountCode)) {discountRate = 0.10;}execution.setVariable("discountRate", discountRate);// 检查是否需要审批if (discountRate > 0.20) {execution.setVariable("approvalNeeded", true);} else {execution.setVariable("approvalNeeded", false);}// 重新计算总价def subtotal = execution.getVariable("subtotal") ?: 0.0;def totalAmount = subtotal * (1 - discountRate);execution.setVariable("totalAmount", totalAmount);]]></flowable:script></flowable:variableListener></extensionElements><startEvent id="startEvent" name="创建报价单"></startEvent><sequenceFlow id="flow1" sourceRef="startEvent" targetRef="configureOrderTask"></sequenceFlow><userTask id="configureOrderTask" name="配置订单" flowable:assignee="${initiator}"></userTask><sequenceFlow id="flow2" sourceRef="configureOrderTask" targetRef="approvalGateway"></sequenceFlow><exclusiveGateway id="approvalGateway" name="需要经理审批吗?"></exclusiveGateway><sequenceFlow id="flowNoApproval" name="否" sourceRef="approvalGateway" targetRef="generateOrderTask"><conditionExpression xsi:type="tFormalExpression">${approvalNeeded == false}</conditionExpression></sequenceFlow><sequenceFlow id="flowNeedsApproval" name="是" sourceRef="approvalGateway" targetRef="managerApprovalTask"><conditionExpression xsi:type="tFormalExpression">${approvalNeeded == true}</conditionExpression></sequenceFlow><userTask id="managerApprovalTask" name="经理审批" flowable:candidateGroups="managers"></userTask><sequenceFlow id="flowApprovalToGenerate" sourceRef="managerApprovalTask" targetRef="generateOrderTask"></sequenceFlow><serviceTask id="generateOrderTask" name="生成正式订单" flowable:class="com.example.GenerateOrderDelegate"></serviceTask><sequenceFlow id="flowToEnd" sourceRef="generateOrderTask" targetRef="endEvent"></sequenceFlow><endEvent id="endEvent" name="订单完成"></endEvent></process><bpmndi:BPMNDiagram id="BPMNDiagram_dynamicQuoteProcess"><bpmndi:BPMNPlane bpmnElement="dynamicQuoteProcess" id="BPMNPlane_dynamicQuoteProcess"><bpmndi:BPMNShape bpmnElement="startEvent" id="BPMNShape_startEvent"><omgdc:Bounds height="36.0" width="36.0" x="100.0" y="160.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="configureOrderTask" id="BPMNShape_configureOrderTask"><omgdc:Bounds height="60.0" width="100.0" x="180.0" y="148.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="approvalGateway" id="BPMNShape_approvalGateway"><omgdc:Bounds height="40.0" width="40.0" x="330.0" y="158.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="managerApprovalTask" id="BPMNShape_managerApprovalTask"><omgdc:Bounds height="60.0" width="100.0" x="420.0" y="240.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="generateOrderTask" id="BPMNShape_generateOrderTask"><omgdc:Bounds height="60.0" width="100.0" x="420.0" y="148.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="endEvent" id="BPMNShape_endEvent"><omgdc:Bounds height="36.0" width="36.0" x="570.0" y="160.0"></omgdc:Bounds></bpmndi:BPMNShape><bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1"><omgdi:waypoint x="136.0" y="178.0"></omgdi:waypoint><omgdi:waypoint x="180.0" y="178.0"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2"><omgdi:waypoint x="280.0" y="178.0"></omgdi:waypoint><omgdi:waypoint x="330.0" y="178.0"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="flowNoApproval" id="BPMNEdge_flowNoApproval"><omgdi:waypoint x="370.0" y="178.0"></omgdi:waypoint><omgdi:waypoint x="420.0" y="178.0"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="flowNeedsApproval" id="BPMNEdge_flowNeedsApproval"><omgdi:waypoint x="350.0" y="198.0"></omgdi:waypoint><omgdi:waypoint x="350.0" y="270.0"></omgdi:waypoint><omgdi:waypoint x="420.0" y="270.0"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="flowApprovalToGenerate" id="BPMNEdge_flowApprovalToGenerate"><omgdi:waypoint x="470.0" y="240.0"></omgdi:waypoint><omgdi:waypoint x="470.0" y="208.0"></omgdi:waypoint></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="flowToEnd" id="BPMNEdge_flowToEnd"><omgdi:waypoint x="520.0" y="178.0"></omgdi:waypoint><omgdi:waypoint x="570.0" y="178.0"></omgdi:waypoint></bpmndi:BPMNEdge></bpmndi:BPMNPlane></bpmndi:BPMNDiagram>
</definitions>