java每日精进 7.21【Uel表达式和流程设计】
1.UEL 表达式
UEL(Unified Expression Language)是 Java EE6 规范的一部分,用于在运行时动态计算值或调用方法,是工作流引擎(如Flowable/Activiti)中实现动态逻辑的核心机制。Flowable 是一个基于 BPMN 2.0 的工作流引擎,它使用 UEL 表达式来处理流程中的动态逻辑,比如:
- 决定任务的审批人:比如,分配任务给流程发起人或其上级领导。
- 控制流程走向:比如,判断请假天数是否超过 3 天来决定流程走哪条路径。
- 调用服务逻辑:比如,调用一个 Spring Bean 的方法来执行业务逻辑。
在 Flowable 中,UEL 表达式可以出现在以下场景:
- Java 服务任务(Java Service Task):调用 Java 方法执行业务逻辑。
- 执行监听器(Execution Listener):在流程的某些事件(如节点开始或结束)触发逻辑。
- 任务监听器(Task Listener):在任务的某些事件(如任务创建或完成)触发逻辑。
- 条件顺序流(Conditional Sequence Flow):决定流程是否走某条路径。
1.1UEL 表达式的两种类型
- 值表达式(Value Expression):
- 用于获取一个值,比如流程变量、Spring Bean 的属性。
- 例子:
- ${myVar}:获取流程变量 myVar 的值。
- ${myBean.myProperty}:获取 Spring Bean myBean 的属性 myProperty 的值。
- 方法表达式(Method Expression):
- 用于调用方法,可以带或不带参数。
- 例子:
- ${printer.print()}:调用 Spring Bean printer 的 print 方法。
- ${myBean.doSomething(myVar, execution)}:调用 myBean 的 doSomething 方法,传入流程变量 myVar 和执行上下文 execution。
1.2.支持的操作
-
逻辑运算:
==
、!=
、>
、<
(仅整数) -
算术运算:
+
、-
等(如${sum = var1 + var2}
) -
高级函数:条件流中支持
var:lessThan()
、var:getOrDefault()
等
1.3默认上下文对象
对象 | 说明 | 使用场景 |
---|---|---|
execution | 当前执行实例(DelegateExecution ),含流程变量、当前节点信息 | 条件流、服务任务 |
task | 当前任务实例(DelegateTask ),仅任务监听器中可用 | 任务监听器 |
authenticatedUserId | 当前登录用户ID | 权限校验 |
initiator | 流程发起人ID | 任务分配 |
1.4 在流程设计器中的应用
1.4.1 任务分配(动态指派审批人)
-
值表达式:在用户任务的
Assignee
属性中直接写表达式。
示例:${initiator} # 将任务分配给流程发起人 ${hrManager} # 使用流程变量"hrManager"的值
-
方法表达式:调用Spring Bean生成审批人。
示例:${bpmTaskAssignLeaderExpression.calculateUsers(execution, 1)} # 分配发起人的一级领导
-
设计器操作:在用户任务属性面板的
Assignee
字段输入表达式(下图以Flowable设计器为例):
https://example.com/uel-assignee.png (注:实际设计器界面需替换为截图)
1.4.2 条件流(动态路由)
-
用法:在序列流的
Condition
属性中写UEL表达式,决定流程走向。
示例:${orderAmount > 10000} # 订单金额超1万走VIP审批 ${status == 'rejected'} # 状态为"驳回"时重提交
-
设计器操作:点击序列流→设置条件类型为Expression→输入表达式:
https://example.com/uel-condition.png
1.4.3 监听器(事件触发逻辑)
-
场景:任务创建/完成时动态设置变量或分配任务。
-
示例(任务创建时设置处理人):
public class MyTaskListener implements TaskListener { @Override public void notify(DelegateTask task) { if ("提交申请".equals(task.getName())) { task.setAssignee("admin"); // 动态指定处理人 } } }
-
设计器操作:在任务监听器的
Expression
字段填${myTaskListener}
(需注册为Spring Bean)16。
1.4.4 表单动态逻辑
-
用法:在表单字段的
Visible
/Required
条件中使用UEL。
示例:${userRole == 'manager'} # 仅经理可见某字段
1.5典型应用场景对比
场景 值表达式示例 方法表达式示例 适用性 任务分配 ${initiator}
${bpmLeaderExpression(execution, 2)}
动态指派审批人 条件路由 ${cost > 1000}
${approvalService.needsVPApproval(order)}
复杂业务规则判断 表单显示逻辑 ${userType == 'admin'}
- 简单条件控制 提示:设计器中可通过快捷键
Ctrl+Space
触发表达式自动补全(支持变量名、函数提示)。通过UEL表达式,流程设计器从静态配置升级为动态规则引擎,大幅提升灵活性。建议结合流程变量监听器(如DelegateTask)调试表达式,确保运行时逻辑符合预期。
2. 流程设计
2.1开始事件
2.1.1空启动事件
定义
空开始事件意味着没有指定启动流程实例的触发条件。它是最常见的一种开始事件,一般需要人工启动,或通过API触发
图形标记
XML内容
<startEvent id="aba1958401251475ca53b3f5d152fde3b" name="空开始"/>
使用示例
空开始事件无需指定触发条件,可以由API触发 runtimeService.startProcessInstanceBy
开头的各种方法发起流程实例。
空启动事件的自定义扩展
- formKey: 引用表单定义,用户需要在启动新流程实例时填写该表单。
<startEvent id="request" flowable:formKey="request" />
2.1.2定时器开始事件
定义
定时器启动事件(timer start event)用于在指定的时间启动一个流程,或者在一定周期内循环启动多次流程,如在2023年8月1日10时整发起年度目标审核流程, 或每月1日0时开始启动财务结算处理流程。当满足设定的时间条件时,定时器开始事件被触发,从而启动流程。
注意 使用定时器开始事件需要开启flowable的作业执行器
configuration.setAsyncExecutorActivate(true);
图形标记
定时开始事件显示为了一个圆圈,内部是一个小时钟
XML内容
定时开始事件的 XML 内容是普通开始事件的声明,包含一个定时器事件子元素。
<startEvent id="timerStart" ><timerEventDefinition><timeDate>2023-07-14T12:12:14</timeDate></timerEventDefinition>
</startEvent>
定义定时器的标签是<timerEventDefinition>...</timerEventDefinition>
,它必须具有以下一个元 素:timeDate,timeDuration,timeCycle。
1. timeDate:设置在指定时间触发
<startEvent id="timerStart"><timerEventDefinition><timeDate>2023-07-14T12:12:14</timeDate></timerEventDefinition></startEvent>
timeDate 是使用 ISO 8601 格式指定一个确定的时间来触发事件,以上配置表示流程会在 2023-07-14T12:12:14 启动起来。
注意:ISO8601 时间格式,如果要加时间需要前面加 T。
2、timeDuration:置指定一个时间段之后执行。
<startEvent id="timerStart"><timerEventDefinition><timeDuration>PT1S</timeDuration></timerEventDefinition></startEvent>
timeDuration 是指定定时器之前要等待多长时间。S 表示秒,M 表示分,D 表示天;P 表示时 间段,T 表示精确到时间的时间段。
注意:时间格式依然为 ISO 8601 格式,一年两个月三天四小时五分六秒内,可以写成 P1Y2M3DT4H5M6S,P 是开始标记,T 是时间和日期分割标记,没有日期只有时间 T 是不能省去 的,比如 1 小时执行一次应该写成 PT1H。
3、通过 timeCycle 设置指定定时器的运行周期
<startEvent id="timerStart"><timerEventDefinition><timeCycle>R2/${EndDate}/PT1M</timeCycle></timerEventDefinition></startEvent>
timeCycle 指定重复执行的间隔,可以用来定期启动流程实例。timeCycle 的设置目前有两种方 式:ISO 8601 和 Cron 表达式。R 表示需要执行的次数,R2/PT1M 表示执行两次,每次间隔 1 分钟。 其中 endDate 是可选的配置,定时器将会在指定的时间停止工作。
注意:使用定时器开始事件需要启动 JobExecutor
注意
1、子流程中不能使用定时器启动事件。定时器是从流程部署开始计时,不需要去启动流程。
2、当定时启动任务已经超过运行时间,再次去部署流程时,就会把它当成一个普通的空启动事件来处理。部署新的流程后,旧版本的流程中的定时事件就会被移除。
2.1.3信号开始事件
定义
信号开始事件在接收到特定的信号后被触发,启动一个流程实例。如果多个流程含有相同信号名称的信号开始事件,那么它们都会被启动。
图形标记
消息开始事件是一个圆圈,中间是一个消息事件图标。图标是白色未填充的,来表示捕获(接收)行为。
XML内容
信号开始事件的 XML 内容是在普通开始事件申请中包含一个 signalEventDefinition 子元素
<!-- 信号定义 --><signal id="theSignal" name="The Signal" /><process id="signalStartProcess"><startEvent id="signalStart" ><!-- 定义为开始信号事件 --><signalEventDefinition signalRef="theSignal" /></startEvent></process>
以上 xml 代码片段中加粗的部分分别定义了 signal、startEvent,信号 signal 的 id 属性值为theSignal,信号开始事件 startEvent 中的 signalEventDefinition 子元素通过设置 signalRef 为 theSignal 引用了该信号。
使用示例
信号启动事件的触发方式通常有以下几种:
1、流程中的抛出信号事件(Signal Intermediate Throwing Event)、信号结束事件(Signal End Event)发出信号,所有拥有相同名字信号启动事件的流程定义都会被启动。
2、通过 API(runtimeService.signalEventReceivedXXX 方法)抛出一个信号,所有拥有相同名字 信号启动事件的流程定义都会被启动。需要为 API 传递的 signalName,是由 signal 元素的 name 属 性决定的名字。signal 元素被 signalEventDefinition 的 signalRef 属性所引用。
3、作为普通开始事件,启动流程。
注意:信号定义的作用域分流程实例有效和全局有效
<signal id="startSigon" name="开始信号" flowable:scope="processInstance" />
<signal id="startSigon" name="开始信号" flowable:scope="global" />
2.1.4消息开始事件
定义
在 BPMN2.0 规范中,消息表示的是流程参与者的沟通信息对象,在一般流程中,流程的各个角色的沟通信息均有可能导致流程的开始。流程开始事件,可以理解为另外一种启动流程的方式或者途径,使用该开始事件,当达到“接收消息”的条件后启动流程。
图形标记
消息开始事件是一个圆圈,中间是一个消息事件图标,图标是白色未填充小信封,来表示捕获(接收)行为。
XML内容
<!-- 消息定义 --><message id="theMessage" name="newMessageName" /><process id="messageStartEventProcess"><startEvent id="messageStart" ><!-- messageEventDefinition --><messageEventDefinition messageRef="theMessage" /></startEvent><!-- --></process>
在以上xml代码片段中,首先定义了一个名称为theMessage的消息 ,消息值为newMessageName,然后在开始事件中使用 messageEventDefinition 元素引用该 message,从而构成了一个消息开始事件。
使用示例
在发布包含一个或多个消息开始事件的流程定义时,需要考虑下面的条件:
1、流程的消息名称必须是唯一的,一个流程定义不得包含多个同名的启动消息。如果两个或以上消息开始事件应用了相同的事件,或两个或以上消息事件引用的消息名称相同,Flowable 会在发布流程定义时抛出异常。
2、消息开始事件的名称在所有已发布的流程定义中不能重复。如果一个或多个消息开始事件引用了相同名称的消息,而这个消息开始事件已经部署到不同的流程定义中,Flowable 就会在发布时抛出一个异常。
3、发布新版流程定义时,会取消上一版本的消息订阅。
4、只有顶级流程(toplevel process)才支持消息开始事件,内嵌子流程不支持消息事件。如果流程被调用环节(callActivity)启动,消息开始事件只支持以下两种情况:在消息开始事件以外,还有一个单独的空开始事件;流程只有一个消息开始事件,没有空开始事件。
✅一、通过消息开始事件启动流程实例有三种方法
✅ 方法 1:
ProcessInstance pi = runtimeService.startProcessInstanceByMessage("orderCreated");
示例说明:
-
假设你在流程定义中有如下消息事件:
<startEvent id="messageStartEvent" name="订单创建">
<messageEventDefinition messageRef="orderCreated"/>
</startEvent>
-
此调用会触发该消息开始事件对应的流程实例。
-
无流程变量,无业务Key。
✅ 方法 2:
Map<String, Object> vars = new HashMap<>();
vars.put("orderId", 12345); vars.put("userId", "user001");
ProcessInstance pi = runtimeService.startProcessInstanceByMessage("orderCreated", vars);
示例说明:
-
同样启动名为
orderCreated
的消息开始事件流程。 -
会携带流程变量,流程启动后可直接获取
orderId
,userId
等变量。
✅ 方法 3:
Map<String, Object> vars = new HashMap<>();
vars.put("orderId", 12345);
ProcessInstance pi = runtimeService.startProcessInstanceByMessage("orderCreated", "businessKey-abc", vars);
示例说明:
-
与上面类似,但同时设置了业务标识
businessKey-abc
。 -
可用于流程实例检索与业务系统关联。
✅ 二、注意事项逐条举例说明
⚠️ 注意事项 1:
如果流程中有多个消息开始事件,
startProcessInstanceByMessage(...)
会匹配messageName
去启动对应的那一个。
示例:
<startEvent id="start1">
<messageEventDefinition messageRef="messageA"/>
</startEvent> <startEvent id="start2">
<messageEventDefinition messageRef="messageB"/>
</startEvent>
runtimeService.startProcessInstanceByMessage("messageA");
✅ 只会启动 messageA
对应的流程开始事件,不会混淆或触发其他消息事件。
⚠️ 注意事项 2:
如果流程定义中有一个空开始事件和一个消息开始事件:
-
使用
startProcessInstanceByKey()
/startProcessInstanceById()
默认触发空开始事件; -
只有使用
startProcessInstanceByMessage(...)
才会触发消息开始事件。
示例:
<startEvent id="startEmpty"/>
<!-- 空开始事件 -->
<startEvent id="startMessage">
<messageEventDefinition messageRef="msg1"/>
</startEvent>
调用:
runtimeService.startProcessInstanceByKey("myProcess"); // 👉 触发空开始事件 runtimeService.startProcessInstanceByMessage("msg1"); // 👉 触发消息事件
⚠️ 注意事项 3:
如果流程中有多个消息开始事件但没有空开始事件,调用
startProcessInstanceByKey(...)
会抛异常。
示例:
<startEvent id="start1">
<messageEventDefinition messageRef="msgA"/>
</startEvent> <startEvent id="start2">
<messageEventDefinition messageRef="msgB"/>
</startEvent>
调用:
runtimeService.startProcessInstanceByKey("myProcess"); // ❌ 抛出异常:流程没有默认启动入口
必须通过如下方式启动:
runtimeService.startProcessInstanceByMessage("msgA");
⚠️ 注意事项 4:
如果流程中只有一个消息开始事件,调用
startProcessInstanceByKey(...)
也会使用它来启动流程。
示例:
<startEvent id="startMsg">
<messageEventDefinition messageRef="onlyMessage"/>
</startEvent>
调用:
runtimeService.startProcessInstanceByKey("processWithSingleMsg"); // ✅ 成功,使用消息事件启动
⚠️ 注意事项 5:
如果启动流程是被 CallActivity 调用的子流程,且该子流程中存在多个消息开始事件,则必须有一个空开始事件,或仅有一个消息事件。否则抛出异常。
❌ 错误示例:
子流程:
<startEvent id="start1">
<messageEventDefinition messageRef="msg1"/>
</startEvent>
<startEvent id="start2">
<messageEventDefinition messageRef="msg2"/>
</startEvent>
👉 抛出异常,因无法判断子流程由谁触发。
✅ 正确示例:
<startEvent id="emptyStart"/>
<!-- 空开始事件 -->
<startEvent id="startMsg">
<messageEventDefinition messageRef="someMessage"/>
</startEvent>
或者:
<!-- 只有一个消息事件 -->
<startEvent id="startMsg">
<messageEventDefinition messageRef="someMessage"/>
</startEvent>
✅ 总结图示:
流程配置 | 使用 byMessage 启动 | 使用 byKey /byId 启动 |
---|---|---|
仅一个消息事件 | ✅ 支持 | ✅ 自动使用该事件 |
一个消息事件 + 空事件 | ✅ 支持 | ✅ 使用空事件 |
多个消息事件,无空事件 | ✅ 支持 | ❌ 报错 |
子流程中多个消息事件 | ❌ 不支持 | ❌ 报错,需有空事件或仅有一个 |
2.1.5错误开始事件
定义
错误开始事件可以触发一个异常子流程,它总是在另外一个流程在异常结束的时候触发。BPMN2.0 规定了错误开始事件只能使用在事件子流程(Event Sub-Process)中,该该事件不能 使用在其它流程中,包括最高级流程(Top-Level Process)、嵌套子流程(Sub-Process)和调用子流程(Call Activity)。
注意,BPMN 错误与 Java 异常不是一回事,BPMN 错误事件是建模业务异常(business exceptions)的方式,二者没有直接关联。
图形图标
错误开始事件是一个圆圈,包含一个错误事件标记。标记是白色未填充的,来表示捕获(接收)行为。
XML内容
<error id="errorStart" errorCode="501" />
<process id="errorStartEventProcess"><startEvent id="errorStartEvent" ><errorEventDefinition errorRef="theError" /></startEvent>
</process>
在以上 xml 代码片段中,首先定义了一个 id 属性值为 theError 的错误,然后在开始事件中使用 errorEventDefinition 元素引用该 error,从而构成了一个错误开始事件。
注意:错误开始事件不能独立存在,必须是其他事件的子流程。
使用示例
两种方式触发错误开始事件
1、错误结束事件抛出错误(注:错误结束事件也选择的是同一个错误定义)
2、自动执行主流程的服务任务抛出错误,接下来子流程的错误开始事件捕获到错误后执行。
@Slf4j
public class AutomaticReviewService implements JavaDelegate {@Overridepublic void execute(DelegateExecution execution) {String healthCodeStatus = (String) execution.getVariable("healthCodeStatus");if (!"green".equals(healthCodeStatus)) {String errorCode = "500";log.error("健康码异常,抛出BPMN错误,errorCode为:{}", errorCode);throw new BpmnError(errorCode);}}
}
2.2 任务
2.2.1用户任务(UserTask)
定义
顾名思义,用户任务是需要人工参与处理的。当流程执行到用户任务节点时,流程引擎会给指定的用户(办理人或候选人)或一组用户(候选组)创建待处理的任务项,等待用户的处理。 用户任务的参与者类型分为两种:⑴分配到一个用户(私有任务);⑵共享给多个用户(共享任务)。大部分的流程场景,一个用户任务通常被具体指派一个用户,在 Flowable 中称为办理人。另外在一些业务处理场景中,一个任务可以被共享给多个人,在 Flowable 中通过指派给多个候选人/ 候选组来实现,这类任务在流程引擎只创建一个任务实例,所有被共享的人都可以查询任务,候选人中的用户有权认领(claim)该任务并且完成该任务,当任务被领取之后,其他候选人即无法再看到此任务。 一个用户任务只允许分配一个办理人,但可以分配多个候选人/候选组。
图形标记
用户任务用左上角有一个小用户图标的标准任务(圆角矩形)表示。
XML标记
用户任务在XML中如下定义。其中id是必须属性,name是可选属性。
<userTask id="theTask" name="Important task" />
也可以为用户任务添加描述(description)。事实上任何BPMN 2.0元素都可以有描述。描述由documentation元素定义。
<userTask id="theTask" name="Schedule meeting" ><documentation>这是描述信息</documentation>
</userTask>
可以使用标准Java方式获取描述文本:task.getDescription()
#属性描述
#分配给办理人(assignee)
用户任务可以直接分配给一个用户,这个任务只能出现在该用户的个人任务列表中,而不会出现在其他人的任务列表中。只有这个用户能查看和办理这个任务,我们称之为办理人。
<userTask id="Activity_1hpkgvi" name="经理" flowable:assignee="${udept.code}"><extensionElements><flowable:assigneeType>static</flowable:assigneeType></extensionElements>
</userTask>
<userTask id="Activity_1hpkgvi" name="经理" flowable:candidateUsers="10000"><extensionElements><flowable:assigneeType>idm</flowable:assigneeType><flowable:idmCandidateUsers>[{"id":"1","name":"易烊千玺","code":"10000","sex":0,"mobile":null,"companyId":"1","companyName":"中国石化","deptId":"27","deptName":"领导班子"}]</flowable:idmCandidateUsers></extensionElements>
</userTask>
#到期日期(dueDate)
每个任务都可以使用一个字段标志该任务的到期日期(dueDate)。可以使用查询API,查询在给定日期前或后到期的任务。 可以在任务定义中使用扩展指定表达式,以在任务创建时设定到期日期。该表达式必须解析为java.util.Date,java.util.String (ISO8601格式),ISO8601时间长度(例如PT50M),或者null。例如,可以使用在流程里前一个表单中输入的日期,或者由前一个服务任务计算出的日期。如果使用的是时间长度,则到期日期基于当前时间加上给定长度计算。 例如当dueDate使用“PT30M”时,任务在从现在起30分钟后到期。
<userTask id="Activity_0zhp33g" name="经理" flowable:dueDate="P30M">
</userTask>
也可使用变量设置值:
<userTask id="Activity_0zhp33g" name="经理" flowable:dueDate="${dateVariable}">
</userTask>
任务的到期日期也可以使用TaskService,或者在TaskListener中使用传递的DelegateTask修改。
#设置到期时间示例
#用户指派
用户任务可以直接指派(assign)给用户。可以定义humanPerformer子元素来实现。humanPerformer需要resourceAssignmentExpression来实际定义用户。目前,只支持formalExpressions。
<process >...<userTask id='theTask' name='important task' ><humanPerformer><resourceAssignmentExpression><formalExpression>kermit</formalExpression></resourceAssignmentExpression></humanPerformer></userTask>
</process>
只能指定一个用户作为任务的humanPerformer。在Flowable术语中,这个用户被称作办理人(assignee)。拥有办理人的任务,在其他人的任务列表中不可见,而只能在该办理人的个人任务列表中看到。 可以通过TaskService获取特定用户办理的任务:
List<Task> tasks = taskService.createTaskQuery().taskAssignee("kermit").list();
#演示demo
用户任务
2.2.2 手动任务
#定义
手动任务是预期在没有任何业务流程执行引擎或任何应用程序的帮助下执行的任务,它用于建模那些引擎不需要知道的人所 做的工作,以及那些不存在已知系统或 UI 界面的人所做的工作,一般完善流程结构描述,不被引擎执行。例如,电话技术 人员在客户位置安装电话。手工任务就是一个自动执行的过程,流程引擎只是记录相关的流程历史数据, 它用 taskService 查询不到的。
#图形标记
手工任务显示为一个普通任务(圆角矩形),左上角是一个手型小图标,如图所示:
#XML标记
手动任务的 XML 表示格式如下:
<manualTask id="manualTask1" name="手动任务" />
#使用示例
下面我们看一个手动任务的使用示例,设计如图所示流程,这是一个现场兑奖示例流程, 当客户发起申请后,首先到达“管理员审核”用户任务节点,操作完成后流转到“奖品发放”手动 任务节点,自动往下执行到流程结束。
流程对应的 XML 内容如下
<process id="ManualTaskProcess" name="任务-手动任务" isExecutable="true"><startEvent id="af0d8560213fd4d8b9710b5188cdc6560"><extensionElements><flowable:formData /></extensionElements></startEvent><userTask id="a35517e47548f45d49517458e1aab2be1" name="奖品兑换申请"><extensionElements><flowable:formData /><flowable:assigneeType>static</flowable:assigneeType></extensionElements></userTask><sequenceFlow id="a90f46869ab7e47f5a5e1c11745fcb3d5" sourceRef="af0d8560213fd4d8b9710b5188cdc6560" targetRef="a35517e47548f45d49517458e1aab2be1" /><endEvent id="Event_1dzfc7c" /><sequenceFlow id="Flow_0wcdlm3" sourceRef="Activity_0yvfkjj" targetRef="Event_1dzfc7c" /><manualTask id="Activity_0yvfkjj" name="奖品发放"><extensionElements><flowable:executionListener class="com.dragon.test.bpmn.listener.ManualTaskExecutionListener" event="start" /><flowable:assigneeType>static</flowable:assigneeType><flowable:formData /></extensionElements></manualTask><userTask id="Activity_1tmhc8f" name="主办方审核"><extensionElements><flowable:formData /><flowable:assigneeType>static</flowable:assigneeType></extensionElements></userTask><sequenceFlow id="Flow_05wn51h" sourceRef="a35517e47548f45d49517458e1aab2be1" targetRef="Activity_1tmhc8f" /><sequenceFlow id="Flow_0f26uqe" sourceRef="Activity_1tmhc8f" targetRef="Activity_0yvfkjj" />
</process>
在以上流程定义中,手动任务 manualTask1 使用 flowable:executionListener 元素定义了一个执行 监听器,该元素并不属于 BPMN2.0 规范,而是 Flowable 扩展出来的元素。在这里设置的执行监听 器会在节点开始(event 属性为 create)时执行。关于执行监听器更详细的介绍和用法。 这里例子中的执行监听器 ManualTaskExecutionListener 内容如下:
@Slf4j
public class ManualTaskExecutionListener implements ExecutionListener {@Overridepublic void notify(DelegateExecution execution) {//获取当前节点信息FlowElement currentFlowElement = execution.getCurrentFlowElement();log.info("到达手动任务,当前节点名称:{},备注:{}", currentFlowElement.getName(), currentFlowElement.getDocumentation());log.info("处理结果:奖品线下发放完成!");}
}
流程到达手动任务后,自动执行往下流转了。
#演示demo
手动任务
2.2.3 接收任务
#定义
接收任务是一种简单任务,它会等待对应消息的到达。当流程执行到达接收任务时,流程状态会持久化到数据库中,这意味 着该流程将一直处于等待状态,直到引擎接收到一个特定的消息为止,该消息将触发离开接收任务继续往下执行。
#图形标记
接收任务显示为一个普通任务(圆角矩形),左上角是一个消息小图标,如图所示:
需要在注意的是,接收任务中的消息图标是白色的(黑色的消息图标表示已经发送的意思)。
#XML标记
接收任务的 XML 表示格式如下:
<receiveTask id="receiveTask1" name="接收任务" />
#使用示例
流程到达接收任务后,会进入一个等待状态,通常用于由外部完成的但需要耗费一定时间的工作,当完成工作后, 要使得流程继续往下执行,可以调用 runtimeService.trigger(executionId)方法,其中executionId 是执行到此接收任务的执行实例的 id。下面我们看一下接收任务的使用示例,设计如下图所示流程,这是一个账号激活示例流程, 当客户发起申请后,首先到达“管理员审核”用户任务节点,操作完成后流转到“等待激活结果”接收任务节点,直到接收到 对应消息后继续往下执行到流程结束。
流程对应的 XML 内容如下
<process id="receiveTaskProcess" name="任务-接收任务" isExecutable="true"><startEvent id="a6f37f9b227774dd68879a4cae79865a8" /><userTask id="a9a3ceaf2c4d64de2af1f861772ac516a" name="账号激活申请"><extensionElements><flowable:formData /><flowable:assigneeType>static</flowable:assigneeType></extensionElements></userTask><sequenceFlow id="a08c7bb0990b14e6ab2d878a8ec43a303" sourceRef="a6f37f9b227774dd68879a4cae79865a8" targetRef="a9a3ceaf2c4d64de2af1f861772ac516a" /><receiveTask id="Activity_1uxmlgz" name="等待激活结果"><extensionElements><flowable:executionListener class="com.dragon.test.bpmn.listener.ReceiveTaskExecutionListener" event="end" /><flowable:assigneeType>static</flowable:assigneeType><flowable:formData /></extensionElements></receiveTask><userTask id="Activity_1g60ycx" name="管理员审核"><extensionElements><flowable:formData /><flowable:assigneeType>static</flowable:assigneeType></extensionElements></userTask><endEvent id="Event_1vyjmzt" /><sequenceFlow id="Flow_0be9ua9" sourceRef="a9a3ceaf2c4d64de2af1f861772ac516a" targetRef="Activity_1g60ycx" /><sequenceFlow id="Flow_1xf4w9a" sourceRef="Activity_1g60ycx" targetRef="Activity_1uxmlgz" /><sequenceFlow id="Flow_0v1w6nx" sourceRef="Activity_1uxmlgz" targetRef="Event_1vyjmzt" />
</process>
在以上流程定义中,接收任务 Activity_1uxmlgz 使用 flowable:executionListener 元素定义了一个执行监听器, 该元素并不属于 BPMN2.0 规范,而是 Flowable 扩展出来的元素。在这里设置的执行监听器会在节点结束 (event 属性为 end)时执行。关于执行监听器更详细的介绍和用法。这里例子中的执行监 听器 ReceiveTaskExecutionListener 内容如下:
@Slf4j
public class ReceiveTaskExecutionListener implements ExecutionListener {@Overridepublic void notify(DelegateExecution execution) {FlowElement currentFlowElement = execution.getCurrentFlowElement();log.info("当前为接收任务,节点名称:{},备注:{}", currentFlowElement.getName(), currentFlowElement.getDocumentation());String result = (String)execution.getVariable("result");log.info("接收任务已被触发,处理结果为:{}", result);}
}
2.2.4 脚本任务
定义
脚本任务(Script Task)是一种自动执行的活动。当流程执行到达脚本任务时,会执行相应的 脚本,完毕后继续执行后继路线。脚本任务无须人为参与,可以通过定义脚本实现自定义的业务逻辑。
图形标记
脚本任务显示为一个普通任务(圆角矩形),左上角是一个脚本小图标,如图所示:
XML标记
脚本任务由 scriptTask 元素定义,需要指定 script 和 scriptFormat,例如:
<scriptTask id="scriptTask1" name=" " scriptFormat="groovy"><script>sum = 0for (i in inputArray) {sum += i}</script>
</scriptTask>
其中,scriptFormat 属性表示脚本格式,其值必须兼容 JSR-223(Java 平台的脚本语言),Flowable 支持三种脚本任务类型:javascript、groovy、juel。默认情况下,javascript 已经包含在 JDK 中,因 此不需要额外的依赖。如果想使用其它兼容 JSR-223 的脚本引擎,需要把对应的 jar 包添加到 classpath 下,并使用合适的名称。比如,Flowable 单元测试经常使用 groovy,因为其语法与 Java十 分相似。脚本任务通过 script 子元素配置需要执行的脚本。 需要注意的是,groovy 脚本引擎放在 groovy-all.jar 中。在 groovy 2.0 版本之前,脚本引擎是 groovy jar 的一部分,因此,使用时必须添加如下依赖:
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.x.x<version>
</dependency>
使用示例
XML内容
<process id="JavaScriptDynamicScriptTaskProcess" name="任务-脚本任务" isExecutable="true"><startEvent id="a3be4f7ffcaa34d91847e02d1d3ae4506" /><sequenceFlow id="a5c28b30e0b984b37a14113371149b0ad" sourceRef="a3be4f7ffcaa34d91847e02d1d3ae4506" targetRef="aea9fea5480ca4231a961dd3696580336" /><scriptTask id="aea9fea5480ca4231a961dd3696580336" name="脚本JavaScript" scriptFormat="JavaScript" flowable:autoStoreVariables="false"><script>var sum = a + b;execution.setVariable("sum", sum); </script></scriptTask><userTask id="Activity_0f27pu9" name="任务审批" flowable:assignee="${applyer}"><extensionElements><flowable:formData /><flowable:assigneeType>static</flowable:assigneeType></extensionElements></userTask><endEvent id="Event_1m6e3rb" /><sequenceFlow id="Flow_0fomqtm" sourceRef="Activity_0f27pu9" targetRef="Event_1m6e3rb" /><sequenceFlow id="Flow_0f808iq" sourceRef="aea9fea5480ca4231a961dd3696580336" targetRef="Activity_0rmiwin" /><sequenceFlow id="Flow_01o6dpp" sourceRef="Activity_0rmiwin" targetRef="Activity_0ihtdoy" /><scriptTask id="Activity_0rmiwin" name="脚本Groovy" scriptFormat="groovy" flowable:autoStoreVariables="false"><script>amass = a * b;execution.setVariable("amass", amass);</script></scriptTask><scriptTask id="Activity_0ihtdoy" name="脚本EL" scriptFormat="juel" flowable:resultVariable="reslut" flowable:autoStoreVariables="false"><script>${amass+sum}</script></scriptTask><sequenceFlow id="Flow_0vy1824" sourceRef="Activity_0ihtdoy" targetRef="Activity_03detgh" /><scriptTask id="Activity_03detgh" name="结果逻辑组合" scriptFormat="groovy" flowable:autoStoreVariables="true"><script>applyer = "";if(reslut>10000){applyer = "张三";}else{applyer = "李四";}</script></scriptTask><sequenceFlow id="Flow_1bgd7y7" sourceRef="Activity_03detgh" targetRef="Activity_0f27pu9" />
</process>
2.3 服务
2.3.1 服务任务
定义
服务任务(Service Task)是一个自动化任务,无须人为参与,一般被用作调用服务。当流程执行到服务任务 时,可以自动执行编写的 Java 程序实现自定义的业务逻辑,完毕后继续执行后继路线。
图形标记
服务任务显示成一个普通任务(圆角矩形),左上角有一个小齿轮图标,如图所示:
XML内容
服务任务由 serviceTask 元素定义,Flowable 提供了 3 种方法来声明 java 调用逻辑。
1、通过 flowable:class 属性指定一个 Java 类 通过这种方式指定一个在流程执行时调用的 Java 类,需要在 serviceTask 的 flowable:class 属性中 指定合法的全路径类名,该类必须实现 JavaDelegate 或者 ActivityBehavior 接口。
1.1、指定为实现了 JavaDelegate 接口的类
<serviceTask id="serviceTask1" name="服务任务" flowable:class="xxx.xxx.xxx.MyJavaDelegate" />
其中通过 flowable:class 属性指定了调用 Java 类为 xxx.xxx.xxx.MyJavaDelegate,它实现了 org.flowable.engine.delegate.JavaDelegate 类,并重写了 execute 方法:
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
public class MyJavaDelegate implements JavaDelegate {@Overridepublic void execute(DelegateExecution execution) {//编写自己的逻辑}
}
1.2、指定为实现了 ActivityBehavior 接口的类
<serviceTask id="serviceTask2" name=" " flowable:class="xxx.xxx.xxx.MyActivityBehavior" />
其中通过 flowable:class 属性指定了调用 Java 类为 xxx.xxx.xxx.MyActivityBehavior,它实现了 org.flowable.engine.impl.delegate.ActivityBehavior 类,并重写了 execute 方法:
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.delegate.ActivityBehavior;
public class MyActivityBehavior implements ActivityBehavior {@Overridepublic void execute(DelegateExecution execution) {//编写自己的逻辑}
}
通过 flowable:class 属性指定的 Java 类,当流程执行到服务节点时,会调用其 execute 方法执行 定义好的业务逻辑,然后按照默认 BPMN 2.0 中的方式离开该节点。在 execute 方法中如果要用到流程实 例、流程变量等,可以通过 DelegateExecution 进行来获取和操作。
需要注意的是,服务任务通过 flowable:class 属性指定的 Java 类只会创建一个实例,即只在第一 次调用时实例化一个对象,该对象会被复用,而不是每次都会实例化一个对象。所有的流程实例都 会共享相同的类实例,并调用其 execute(DelegateExecution)方法。这就意味着,该 Java 类不能使用 任何成员变量,必须是线程安全的,因为它可以从不同的线程同时执行。这也影响着属性注入的处 理方式,下一节会进行介绍.
流程定义中由服务任务使用 flowable:class 属性指定的类不会在流程部署时实例化。只有当流程 执行第一次到达调用该类的服务节点时,才会创建该类的一个实例。如果找不到类,会抛出一个 FlowableException 异常。
2、通过 flowable:delegateExpression 使用委托表达式指定 可以使用解析为对象的表达式来指定,该对象必须遵循与使用 flowable:class 属性时创建的对象 相同的规则。
<serviceTask id="serviceTask3" name="服务任务" flowable:delegateExpression="${delegateExpressionBean}" />
其中通过 flowable:delegateExpression 属性指定了委托表达式为${delegateExpressionBean}, delegateExpressionBean 是一个实现了 JavaDelegate 接口的 bean,表达式调用之前需要将它初始化到 流程变量中,或者定义在实例的 spring 容器中。委托表达式中只用写 bean 的名称,不需要写方法 名,引擎会自动调用其 execute 方法。
3、通过 flowable:expression 属性使用 UEL 表达式指定 可以通过 flowable:expression 属性指定为 UEL 方法表达式或值表达式,调用一个普通 java bean 的方法或属性,表达式调用之前需要将它初始化到流程变量中,或者定义在实例的 spring 容器中。 这个 bean 不需要实现 org.flowable.engine.delegate.JavaDelegate 接口,表达式中必须指定调用的方法名或属性名。 可以通过flowable:resultVariable="totalMount" 指定返回结果赋值到变量中 3.1、指定为 UEL 方法表达式 表达是可以为无参表达式:
<serviceTask id="serviceTask4"
name="服务任务"
flowable:expression="${businessBean.calculateMount1()}"
flowable:resultVariable="totalMount"/>
上述服务任务定义表示调用 businessBean 对象的 calculateMount1(无参数)方法。 也可以为表达式中的方法传递参数:
<serviceTask id="serviceTask5"
name="服务任务"
flowable:expression="${businessBean.calculateMount2(execution, money)}" />
上述服务任务定义表示会调用 businessBean 对象的 calculateMount2 方法,该方法第一个参数 是 DelegateExecution,在表达式环境中默认名称为 execution,第二个参数传递的是当前流程的名为 money 的变量。 3.2、指定为 UEL 值表达式
<serviceTask id="serviceTask6"
name="服务任务"
flowable:expression="#{businessBean.total}" />
上述服务任务定义会获取 businessBean 的 total 字段的值,实质是调用其 getTotal()方法。
2.3.2 邮件任务
定义
Flowable 支持通过自动的邮件服务任务(Email Task)增强业务流程,它可以向一个或多个收信人发送 邮件,支持 cc,bcc,HTML 内容等。 流程流转到邮件任务时,会给指定的人发送邮件。需要注意的是,邮件任务不是 BPMN 2.0 规范的 “官方”任务,所以也没有专用图标。在 Flowable 中,邮件任务是作为一种特殊的服务任务来实现的。
图形标记
在 Flowable 中邮件任务是在服务任务标签中加入一个type属性指定为邮件任务,BPMNJS的图形统一采用服务图标:
XML内容
邮件任务在 Flowable 中定义为一个专用的服务任务,这个服务任务的 type 属性设置为 mail。邮 件任务的 XML 表示格式如下
<serviceTask id="Activity_1rev938" name="邮件发送" flowable:type="mail"><extensionElements><flowable:field name="to"><flowable:expression>liuwenjun@163.com</flowable:expression></flowable:field><flowable:field name="subject"><flowable:string>发送退款申请邮件</flowable:string></flowable:field><flowable:field name="html"><flowable:expression><html><body><p>${user}发起退款${money}</p></body></html></flowable:expression></flowable:field><flowable:field name="charset"><flowable:string>utf-8</flowable:string></flowable:field></extensionElements></serviceTask>
邮件任务可以通过属性注入的方式配置各种属性,这些属性的值可以使用 EL 表达式,并将在流程执 行时进行解析。邮件任务可以配置如表所示的属性:
属性 | 是否必须 | 描述 |
---|---|---|
to | 是 | 邮件接收者的邮箱地址。可以使用逗号分隔多个接收者邮箱地址 |
from | 否 | 邮件发送人的邮箱地址。如果不提供,会使用默认配置的地址(默认地址配置见下文介绍) |
subject | 否 | 邮件的主题 |
cc | 否 | 邮件抄送人的邮箱地址。可以使用逗号分隔多个抄送人邮箱地址 |
bcc | 否 | 邮件密送人的邮箱地址。可以使用逗号分隔多个密送人邮箱地址 |
charset | 否 | 用于指定邮件的字符集,对很多非英语语言是必须设置的 |
html | 否 | 邮件的 HTML 文本 |
text | 否 | 邮件的内容,用于纯文本邮件。对于不支持富文本内容的客户端,可以与 html 一起使用,邮件客户端可以降级为显式纯文本格式 |
htmlVar | 否 | 存储邮件 HTML 内容的流程变量名。与 html 属性的不同之处在于,这个属性会在邮件任务发送前,使用其内容进行表达式替换 |
textVar | 否 | 存储邮件纯文本内容的流程变量名。与 text 属性的不同之处在于,这个属性会在邮件任务发送前,使用其内容进行表达式替换。 |
ignoreException | 否 | 当处理邮件失败时,是忽略还是抛出 FlowableException。默认值为 false |
exceptionVariableName | 否 | 如果设置 ignoreException = true,而处理邮件失败时,则使用此属性指定名字的变量保存失败信息 |
邮件服务器配置
为了使用邮件任务发送邮件,需要事先为 Flowable 配置支持 SMTP 功能的外部邮件服务器,可以在flowable配置文件中配置如表所示的属性
#配置发送邮箱地址
flowable.mail.server.default-from=xxxx@qq.com
#配置smtp
flowable.mail.server.host=smtp.qq.com
#用户名
flowable.mail.server.username=xxxx
#密码
flowable.mail.server.password=xxxx
#端口
flowable.mail.server.s-s-l-port=465
#是否是SSL
flowable.mail.server.use-ssl=true
#是否是TLS
flowable.mail.server.use-tls=false
2.3.3 骆驼任务
定义
Camel 任务不是 BPMN 2.0 规范定义的官方任务,在 Flowable 中,Camel 任务是作为一种特殊的服务 任务来实现的。主要做路由工作的。
图形标记
由于 Camel 任务不是 BPMN 2.0 规范的“官方”任务,因此没有提供其专用图标。在 bpmnjs 中 Camel 任务我们就使用服务任务代替,如图所示:
XML内容
Camel 任务在 Flowable 中定义为一个专用的服务任务,这个服务任务的 type 属性设置为 camel。 Camel 任务的 XML 表示格式如下:
<serviceTask id="camelTask1" name="Camel " flowable:type="camel" />
以上在流程定义的服务任务上定义 Camel 类型即可,集成逻辑都将通过 Camel 容器委托。
Flowable与Camel集成
本节将具体介绍 Flowable 与 Camel 集成的过程,以及 Flowable 基于 Camel 扩展出来的各种特性 和用法。
- Camel 的配置与依赖 默认情况下,使用 Camel 任务时,Flowable 引擎在 Spring 容器中查找 camelContext Bean。camelContext Bean 用于定义 Camel 容器装载的 Camel 路由,Camel 路由可以在 Spring 配置文件中定义,也可以按照指 定的 Java 包装载路由:
<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring"><packageScan><package>com.dragon.test.bpmn.camel.route</package></packageScan></camelContext>
通过以上配置,在初始化时 CamelContext 时会把 com.dragon.test.bpmn.camel.route 中 的路由定义类(继承自 RouteBuilder,后文会讲解)注册到 CamelContext 对象中。CamelContext 是 Camel 中一个很重要的概念,它横跨了 Camel 服务的整个生命周期,并且为 Camel 服务的工作环境提供支撑, Camel 中的各个 Service 的关联衔接通过 CamelContext 上下文对象完成。 由于 Flowable 的配置文件采用的是 spring bean 配置文件格式,因此在 Flowable 与 Camel 集成时,以上 配置内容可以直接加在 Flowable 的配置文件中。 如果想要定义多个 camelContext,或想使用不同的 Bean 名字,可以在 Camel 任务定义中通过以下方 式指定:
<serviceTask id="camelTask1" name="Camel " flowable:type="camel" />
需要注意的是,如果要使用 Camel 任务,需要在项目中包含 flowable-camel 模块依赖及 Camel 相关依 赖,Maven 依赖定义如下:
<dependency><groupId>org.flowable</groupId><artifactId>flowable-camel</artifactId><version>xxx</version></dependency><dependency><groupId>org.apache.camel</groupId><artifactId>camel-http</artifactId><version>xxx</version></dependency>
- 定义Camel路由 Camel 最重要的特色之一就是路由,路由用于应用中的通信或者应用间通信。Camel 的路由需要通过 我们手动编排的方式,在指定的(或可变的)端点间进行数据的传输、过滤、转换等操作。Camel 路由 易于使用的一个特性是端点 URI,通过指定 URI,可以确定要使用的组件以及该组件是如何被配置的。 然后可以决定将消息发送到由该 URI 配置的组件,或者使用该组件发出消息。 flowable 的 flowable-camel 模块提供了 Camel 与 Flowable 通信的桥梁,当流程流转到 Camel 任务后流程 引擎将调用 Camel 执行对应的路由,同时可以选择把流程变量传递给路由,在路由处理结束后还可以有 选择地把路由得到的结果以流程变量的方式回传给流程实例。 我们可以通过 Java DSL 构建 Camel 路由(也可通过 XML 配置,这里不做介绍),需要继承 org.apache.camel.builder.RouteBuilder 类,然后实现其中的 configure 方法。Flowable 与 Camel 集成后,一个 典型的路由定义类如下:
public class GetIpInfoCamelCallRoute extends RouteBuilder {@Overridepublic void configure() {from("flowable:CamelTaskProcessTest:afef07cc915df4771a54048fe871e9c9e?copyVariablesToProperties=true").toD("http://ip-api.com/json/${exchange.getProperty('ip')}?lang=zh-CN&bridgeEndpoint=true").process(new ResultProcessor());}
}
在以上路由定义类的 configure 方法中,通过 Java 的 DSL 语言(域特定语言)描述路由规则,from 和 to 是两个关键字,Camel 会从 from 声明的起始端点将消息路由至 to 声明的终点。所有的路由都以一 个 from 开始,它接受端点 URI 作为参数。flowable-camel 模块定义了“flowable”类型的路由 URI 协议,以 上面的配置为例,from 端点(endpoint)的格式包含冒号和问号分隔的几个部分,各部分的含义如表 15.3 所示。 flowable-camel 模块提供的 URI 协议
属性 | 描述 |
---|---|
flowable | 协议开头,指向引擎端点 |
CamelTaskProcessTest | 流程定义 Key |
afef07cc915df4771a54048fe871e9c9e | 流程定义中 Camel 服务的 id |
copyVariablesToProperties=true | 路由 URI 的参数 |
- 路由URI配置 我们可以通过在 URI 中附加一些参数,使用 Flowable 提供的几种不同的行为,从而实现干预 Camel 组件的功能。本节将介绍 URI 中支持的参数,包括输入参数和输出参数两类。 3.1. 输入参数 Flowable 提供了三种输入参数,可以用于控制将流程变量复制到 Camel 的策略,可配置的输入参数如 表所示:
参数 对应flowable行为类 描述 copyVariablesToProperties org.flowable.camel.impl.CamelBehaviorDefaultImpl 默认配置,将 Flowable 的流程变量复制为 Camel 参数,在路由中可以通过形如${property.variableName}的表达式获取到参数值。 copyCamelBodyToBody org.flowable.camel.impl.CamelBehaviorCamelBodyImpl 只将名为"camelBody"的 Flowable 流程变量复制为 Camel 消息体。如果 camelBody 的值是 Map 对象,在路由中可以通过形如${body[variableName]}的表达式获取到参数值;如果 camelBody 的值是纯字符,可以使用${body}表达式获取。 copyVariablesToBodyAsMap org.flowable.camel.impl.CamelBehaviorBodyAsMapImpl 把 Flowable 的所有流程变量复制到一个 Map 对象里,作为 Camel 的消息体。,在路由中可以通过形如${body[variableName]}的表达式获取到参数值。 以如下路由规则为例:
from("flowable:CamelTaskProcess:camelTask1?copyVariablesToProperties=true").to("log:org.flowable.camel.examples.SimpleCamelCall");
这里的配置在 URI 中附加了 copyVariablesToProperties=true,表示将 Flowable 的流程变量复制成 Camel 参数。 3.2. 输出参数 同样的,Flowable 提供了几种输出参数,可以用于控制将 Camel 执行结果复制到流程变量的策略,可 配置的输出参数如表所示: flowable-camel 模块提供的 URI 协议:
参数 | 描述 |
---|---|
default | 默认配置。如果 Camel 消息体是一个 Map 对象,则在路由执行结束后将其中每一个属性复制为 Flowable 的流程变量;否则将整个 Camel 消息体复制到名为"camelBody"的流程变量。 |
copyVariablesFromProperties | 将 Camel 参数以相同的名称复制为 Flowable 流程变量。 |
copyVariablesFromHeader | 将 Camel Header 中的内容以相同的名称复制为 Flowable 流程变量。 |
copyCamelBodyToBodyAsString | 与 default 相同,但如果 Camel 消息体不是 Map 对象,则首先将其转换为字符串,然后再复制到名为"camelBody"的流程变量 |
以如下路由规则为例: |
from("flowable:CamelTaskProcess:camelTask1?copyVariablesFromProperties=true").to("log:org.flowable.camel.examples.SimpleCamelCall");
这里的配置在 URI 中附加了 copyVariablesFromProperties=true,表示将 Camel 参数以相同的名称复制 为 Flowable 流程变量中。 4. 异步Camel调用 默认情况下,Camel 任务是同步执行的,流程执行到 Camel 任务后将处于等待状态,直到 Camel 执 行结束并返回结果后,才离开 Camel 任务往下继续流转。如果 Camel 任务执行时间比较长,或者某些场 景下不需要同步执行,则可以使用 Camel 任务的异步功能,将 Camel 任务的 async 参数设置为 true 即可 启用这个功能:
<serviceTask id="camelTask1" name=" Camel " flowable:type="camel"flowable:async="true"/>
设置这个参数后,Camel 路由会由 Flowable 作业执行器异步启动。 5. 通过Camel启动流程 前面几节介绍了如何整合 Camel 与 Flowable,以及两者如何通信,都是先启动 Flowable 流程实例,然 后在流程实例中启动 Camel 路由。反过来,也可以通过 Camel 任务启动或调用流程实例,其 Camel 的路 由规则可以这么设计:
from("flowable:ParentProcess:camelTaskForStartSubprocess").to("flowable:subProcessCreateByCamel");
其中,from 声明的起始端点的 URI 分为三个部分:"flowable"协议开头,父流程定义 key,Camel 任务 id;to 声明的终止端点的 URI 包含两个部分:"flowable"协议开头,子流程定义 key。
使用示例
下面我们看一个使用 Camel 任务的示例流程,如图所示,用于调用外部第三方服务自动化地 获取 IP 信息。流程发起后首先通过 Camel 任务调用外部 Web 服务查询 IP 信息,然后通过邮件任务发送 查询结果给申请人
Camel 路由配置整合到 Flowable 的配置文件中内容如下:
<process id="CamelTaskProcessTest" name="服务-骆驼任务" isExecutable="true"><startEvent id="a4192d69b9d5a4d1faf42b7c9741d0d48" /><sequenceFlow id="a0db08b7fe8d74803b11b59179c6e0a57" sourceRef="a4192d69b9d5a4d1faf42b7c9741d0d48" targetRef="afef07cc915df4771a54048fe871e9c9e" /><serviceTask id="afef07cc915df4771a54048fe871e9c9e" name="获取Ip信息" flowable:type="camel"><extensionElements><flowable:formData /><flowable:assigneeType>static</flowable:assigneeType></extensionElements></serviceTask><sequenceFlow id="Flow_1ce3l0r" sourceRef="afef07cc915df4771a54048fe871e9c9e" targetRef="Activity_1n9nrw6" /><serviceTask id="Activity_1n9nrw6" name="发送邮件" flowable:type="mail"><extensionElements><flowable:field name="to"><flowable:expression>liuwenjun05101@163.com</flowable:expression></flowable:field><flowable:field name="subject"><flowable:string>IP信息查询结果</flowable:string></flowable:field><flowable:field name="html"><flowable:expression><html><body><p>用户&lt;b&gt;${userName}&lt;/b&gt;你好,&lt;br/&gt;</p><p>你查询的IP&lt;b&gt;${ip}&lt;/b&gt;的信息为:&lt;br/&gt;</p><p>省份:${regionName} 城市:${city}</p><p>互联网服务提供商:${isp}</p></body></html></flowable:expression></flowable:field><flowable:field name="charset"><flowable:string>utf-8</flowable:string></flowable:field></extensionElements></serviceTask><endEvent id="Event_1l6yldo" /><sequenceFlow id="Flow_1h6rd9m" sourceRef="Activity_1n9nrw6" targetRef="Event_1l6yldo" /></process>
在以上配置中:
-
流程引擎配置使用的是 SpringProcessEngineConfiguration,这是因为 Flowable Camel集成时, 需要通过 SpringProcessEngineConfiguration 获取 camelContext,相关细节读者可自行查看源代码。
-
Camel路由是通过Spring的环境下扫描路由配置实现的,Spring会扫描包路径 com.dragon.test.bpmn.camel.route下的 Route 类加载到 camelContext 中。
设计Came路由代码
Camel 路由 Route 的代码如下:
package com.didichuxing.bpm.demo.chapter15.camel.route;
import com.didichuxing.bpm.demo.chapter15.camel.processor.ResultProcessor;
import com.didichuxing.bpm.demo.chapter15.camel.processor.TransformProcessor;
import org.apache.camel.builder.RouteBuilder;
public class GetIpInfoCamelCallRoute extends RouteBuilder {
@Override
public void configure() throws Exception {from("flowable:CamelTaskProcess:camelTask1?copyVariablesToProperties=true").toD("http://ip-api.com/json/${property.ip}?lang=zh-CN&bridgeEndpoint=true").process(new ResultProcessor());
}
}
在以上代码中:
- from 声明的起始端点的 URI 中,flowable 为协议开头,CamelTaskProcess 为流程定义 key, camelTask1 为 Camel 任务 id,输入参数配置的 copyVariablesToProperties=true 表示将 Flowable 的流程 变量复制成 Camel 参数,输出参数使用的默认配置。
- 终止端点采用 toD 声明,它允许通过表达式的方式来动态定义消息的接收节点,这里使用了 表达式${property.ip},表示从 Camel 参数中获取 ip 属性的值。
- 路由中使用到了自定义 Processor 处理器 ResultProcessor。Processor 处理器是 Camel 中的一 个重要元素,它用于接收从控制端点、路由选择条件又或者另一个处理器的 Exchange 中传来的消 息信息,并进行处理。我们可以在自定义的 Processor 处理器中做很多事情,比如这里使用它来进 行外部服务返回结果数据格式的转换,代码如下
import com.alibaba.fastjson.JSON;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import java.util.Map;
import java.util.stream.Collectors;
public class ResultProcessor implements Processor {public void process(Exchange exchange) {// camelString camelCallResult =exchange.getIn().getBody(String.class);// camelSystem.out.println("camel" + camelCallResult);// MapMap<String, String> camelCallResultMap = JSON.parseObject(camelCallResult,Map.class);//Map<String, String> resultMap =camelCallResultMap.entrySet().stream().filter(map -> "country".equals(map.getKey()) ||"regionName".equals(map.getKey())|| "city".equals(map.getKey()) ||"isp".equals(map.getKey())).collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));// Camel Mapexchange.getOut().setBody(resultMap, Map.class);}
}
在以上代码中,首先获取访问外部服务返回的结果(是一个 json 字符串),然后将其转成 Map 对象,接下来对其 key 进行过滤仅保留 country、regionName、city 和 isp 组成一个新的 Map,最后 将该 Map 作为 Camel 消息体回传给 Flowable。
2.3.4 Http服务任务
定义
Http 任务不是 BPMN 2.0 规范定义的官方任务,在 Flowable 中,Http 任务是作为一种特殊的服务 任务来实现的,主要调用Http服务使用。
图形标记
由于 Http 任务不是 BPMN 2.0 规范的“官方”任务,因此没有提供其专用图标, 而是复用了服务任务的图标:
XML内容
Http 任务在 Flowable 中定义为一个专用的服务任务,这个服务任务的 type 属性设置为 http。 Http 任务的 XML 表示格式如下:
<serviceTask id="Activity_0jl7867" name="调用Http服务" flowable:type="http" flowable:parallelInSameTransaction="true"><extensionElements><flowable:field name="requestMethod"><flowable:string>GET</flowable:string></flowable:field><flowable:field name="requestUrl"><flowable:string>https://www.baidu.com</flowable:string></flowable:field><flowable:field name="requestHeaders"><flowable:string>Content-Type: application/json</flowable:string></flowable:field><flowable:field name="disallowRedirects"><flowable:string>false</flowable:string></flowable:field><flowable:field name="ignoreException"><flowable:string>false</flowable:string></flowable:field><flowable:field name="saveResponseParametersTransient"><flowable:string>false</flowable:string></flowable:field><flowable:field name="saveResponseVariableAsJson"><flowable:string>false</flowable:string></flowable:field></extensionElements>
</serviceTask>
以上在流程定义的服务任务上定义 Http 类型即可。
参数说明:
属性名称 | 属性说明 |
---|---|
requestUrl | 请求的url |
requestHeaders | 请求头信息 |
requestMethod | 请求方式 |
requestBody | 请求参数 |
disallowRedirects | 是否允许重定向 |
ignoreException | 是否忽略异常 |
saveResponseParametersTransient | 是否保存瞬时变量 |
saveResponseVariableAsJson | 是否保存json格式 |
saveResponseParameters | 是否返回结果保持到流程实例变量中 |
responseVariableName | 返回结果保持到流程变量的名称 |
注意:
1、忽略异常设置成是,组件会停留在这里
2、保留结果:汇报请求的结果保持到流程变量中去
2.3.5 Rest服务任务
定义
Rest 任务不是 BPMN 2.0 规范定义的官方任务,在 Flowable 中,Rest 任务是作为一种特殊的服务 任务来实现的,主要调用rest服务使用。
图形标记
由于 Rest 任务不是 BPMN 2.0 规范的“官方”任务,因此没有提供其专用图标, 而是复用了服务任务的图标:
XML内容
Rest 任务在 Flowable 中定义为一个专用的服务任务,这个服务任务的 type 属性设置为 rest。 Rest 任务的 XML 表示格式如下:
<serviceTask id="Activity_0tuo1jl" name="Rest回调" flowable:type="rest"><extensionElements><flowable:field name="requestUrl"><flowable:string>https://www.baidu.com</flowable:string></flowable:field><flowable:field name="requestMethod"><flowable:string>POST</flowable:string></flowable:field><flowable:field name="ignoreException"><flowable:string>false</flowable:string></flowable:field><flowable:field name="saveResponseParameters"><flowable:string>false</flowable:string></flowable:field></extensionElements>
</serviceTask>
以上在流程定义的服务任务上定义 Rest 类型即可。
参数说明:
属性名称 | 属性说明 |
---|---|
requestUrl | 请求的url |
requestHeaders | 请求头信息 |
requestMethod | 请求方式 |
requestBody | 请求参数 |
ignoreException | 是否忽略异常 |
saveResponseParameters | 是否返回结果保持到流程实例变量中 |
responseVariableName | 返回结果保持到流程变量的名称 |
注意:
1、忽略异常设置成是,组件会停留在这里
2、保留结果:汇报请求的结果保持到流程变量中去
2.3.6 微服务任务
定义
Sc 任务不是 BPMN 2.0 规范定义的官方任务,在 Flowable 中,Sc 任务是作为一种特殊的服务 任务来实现的,主要调用springcloud的微服务使用。
图形标记
由于 Sc 任务不是 BPMN 2.0 规范的“官方”任务,因此没有提供其专用图标, 而是复用了服务任务的图标:
XML内容
Sc 任务在 Flowable 中定义为一个专用的服务任务,这个服务任务的 type 属性设置为 sc。 Sc 任务的 XML 表示格式如下:
<serviceTask id="Activity_1ip6ske" name="微服务回调" flowable:type="sc"><extensionElements><flowable:field name="serviceId"><flowable:string>flow-center</flowable:string></flowable:field><flowable:field name="url"><flowable:string>/api/order/getOrderInfo</flowable:string></flowable:field><flowable:field name="method"><flowable:string>POST</flowable:string></flowable:field><flowable:field name="params"><flowable:expression>{"id":"liuwenjun","name":"${name}"}</flowable:expression></flowable:field><flowable:field name="ignoreException"><flowable:string>false</flowable:string></flowable:field><flowable:field name="saveResponseParameters"><flowable:string>false</flowable:string></flowable:field></extensionElements>
</serviceTask>
以上在流程定义的服务任务上定义 sc 类型即可。
参数说明:
属性名称 | 属性说明 |
---|---|
serviceId | 微服务的serviceId |
url | 请求的url |
method | 请求方式 |
params | 请求参数 |
ignoreException | 是否忽略异常 |
saveResponseParameters | 是否返回结果保持到流程实例变量中 |
responseVariableName | 返回结果保持到流程变量的名称 |
注意:
1、忽略异常设置成是,组件会停留在这里
2、保留结果:汇报请求的结果保持到流程变量中去
2.3.7 决策任务
顺序流是一端带有箭头的实线,可在流程图中连接流程内的各个元素,并显示各个元素的执行顺序。 决策任务是集成DMN组件,集成BPMN使用,通过流程变量输入经过决策引擎输出变量
Flowable 支持的决策有:
- 决策表
- 决策服务
决策表定义
决策任务是 不是BPMN流程定义元素。决策任务通过流程引擎的全局变量输入条件经过DMN引擎 得到输出结果,输出结果存入流程引擎的全局变量中
图形标记
决策任务表示为一个决策表格如图所示:
XML内容
决策任务在 Flowable 中定义为一个专用的服务任务,这个服务任务的 type 属性设置为 dmn。 决策 任务的 XML 表示格式如下:
<bpmn:serviceTask id="Activity_0dleo8v" flowable:type="dmn"><bpmn:extensionElements><flowable:field name="fallbackToDefaultTenant"><flowable:string>false</flowable:string></flowable:field><flowable:field name="decisionTaskThrowErrorOnNoHits"><flowable:string>false</flowable:string></flowable:field><flowable:field name="decisionTableReferenceKey" text="商品折扣决策"><flowable:string>DiscountCalculationDecision</flowable:string></flowable:field></bpmn:extensionElements>
</bpmn:serviceTask>
以上在流程定义的服务任务上定义 dmn 类型即可。
参数说明:
属性名称 | 属性说明 |
---|---|
fallbackToDefaultTenant | 回退到默认租户 |
decisionTaskThrowErrorOnNoHits | 如果未命中任何规则,则引发错误 |
decisionTableReferenceKey | dmn的key |