news 2026/4/20 23:45:10

工作流引擎Activiti或Flowable中Expression的详细解析和具体使用(抄送任务监听器场景)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
工作流引擎Activiti或Flowable中Expression的详细解析和具体使用(抄送任务监听器场景)

文章目录

    • 一、Expression 是什么?
    • 二、Expression 的类型
    • 三、Expression 如何被注入?
    • 四、在 BPMN 文件中配置
    • 五、Expression 的常用方法
    • 六、支持多种表达式类型(完整示例)
    • 七、抄送任务监听器中的使用
    • 八、完整的改进监听器示例

一、Expression 是什么?

在Activiti或Flowable中,Expression是一个表达式对象,它可以用来存储流程定义中配置的表达式。这个表达式可以是固定值,也可以是动态的(比如使用UEL表达式)。在流程定义中,我们可以在监听器配置中设置表达式的值。

二、Expression 的类型

Flowable 支持多种表达式类型:

类型示例说明
固定值user1,user2,user3直接指定的值
变量表达式${userId}从流程变量中获取
方法表达式${bean.method(userId)}调用 Spring Bean 的方法
组合表达式${userService.getCcUsers(processId)}复杂表达式

三、Expression 如何被注入?

在你的代码中:

privateExpressionuserIds;

这个字段会被 Flowable 引擎自动注入,注入的来源是流程定义文件(BPMN)中的配置。

四、在 BPMN 文件中配置

<!-- 方式1:直接指定用户ID(固定值) --><userTaskid="approveTask"name="审批任务"flowable:assignee="zhangsan"><extensionElements><flowable:taskListenerevent="create"class="com.ruoyi.flowable.listener.FlowCopyTaskListener"><flowable:fieldname="userIds"><flowable:string><![CDATA[1001,1002,1003]]></flowable:string></flowable:field></flowable:taskListener></extensionElements></userTask><!-- 方式2:使用变量(动态值) --><userTaskid="approveTask"name="审批任务"><extensionElements><flowable:taskListenerevent="create"class="com.ruoyi.flowable.listener.FlowCopyTaskListener"><flowable:fieldname="userIds"><flowable:expression><![CDATA[${ccUserIds}]]></flowable:expression></flowable:field></flowable:taskListener></extensionElements></userTask><!-- 方式3:调用Spring Bean方法 --><userTaskid="approveTask"name="审批任务"><extensionElements><flowable:taskListenerevent="create"class="com.ruoyi.flowable.listener.FlowCopyTaskListener"><flowable:fieldname="userIds"><flowable:expression><![CDATA[${userService.getCcUsers(execution.getVariable('deptId'))}]]></flowable:expression></flowable:field></flowable:taskListener></extensionElements></userTask>

五、Expression 的常用方法

publicinterfaceExpression{// 获取表达式原文StringgetExpressionText();// 获取解析后的值ObjectgetValue(VariableScopevariableScope);// 设置值voidsetValue(Objectvalue,VariableScopevariableScope);// 检查是否为文字文本booleanisLiteralText();}

六、支持多种表达式类型(完整示例)

@Slf4j@ComponentpublicclassFlowCopyTaskListenerimplementsTaskListener{privateExpressionuserIds;@Overridepublicvoidnotify(DelegateTaskdelegateTask){log.info("抄送任务监听器被触发, 任务ID: {}",delegateTask.getId());// 方法1:直接获取表达式文本(适用于固定值)StringexpressionText=userIds.getExpressionText();log.info("表达式原文: {}",expressionText);// 方法2:获取解析后的值(适用于变量表达式)Objectvalue=userIds.getValue(delegateTask.getExecution());log.info("解析后的值: {}",value);// 解析抄送人员IDList<String>userIdList=parseUserIds(value,expressionText,delegateTask);if(!CollectionUtils.isEmpty(userIdList)){processCopyTask(delegateTask,userIdList);}}/** * 解析用户ID列表 */privateList<String>parseUserIds(Objectvalue,StringexpressionText,DelegateTaskdelegateTask){List<String>userIdList=newArrayList<>();// 情况1:如果值是List类型if(valueinstanceofList){List<?>list=(List<?>)value;for(Objectitem:list){if(item!=null){userIdList.add(item.toString());}}}// 情况2:如果值是字符串(逗号分隔)elseif(valueinstanceofString){StringstrValue=(String)value;if(StringUtils.isNotBlank(strValue)){userIdList=Arrays.asList(strValue.split(","));}}// 情况3:直接使用表达式文本(固定值)elseif(StringUtils.isNotBlank(expressionText)){// 检查是否为变量表达式(以${开头}结尾)if(expressionText.startsWith("${")&&expressionText.endsWith("}")){// 尝试从流程变量中获取StringvariableName=expressionText.substring(2,expressionText.length()-1);ObjectvariableValue=delegateTask.getExecution().getVariable(variableName);if(variableValueinstanceofString){userIdList=Arrays.asList(((String)variableValue).split(","));}}else{// 直接按逗号分隔userIdList=Arrays.asList(expressionText.split(","));}}// 去除空白字符returnuserIdList.stream().map(String::trim).filter(StringUtils::isNotBlank).collect(Collectors.toList());}/** * 处理抄送任务 */privatevoidprocessCopyTask(DelegateTaskdelegateTask,List<String>userIdList){// ... 原有处理逻辑RepositoryServicerepositoryService=SpringUtils.getBean(RepositoryService.class);ISysCopyServicesysCopyService=SpringUtils.getBean(ISysCopyService.class);// ... 其他代码}}

七、抄送任务监听器中的使用

在抄送任务监听器中,userIds是一个Expression类型的属性,它会在流程引擎运行到该任务节点时,根据流程定义中配置的表达式进行解析。

在流程定义XML中,配置监听器如下(flowable为例):

<userTaskid="myTask"name="My Task"><extensionElements><flowable:taskListenerevent="create"class="com.ruoyi.flowable.listener.FlowCopyTaskListener"><flowable:fieldname="userIds"><flowable:string><![CDATA[${抄送人员表达式}]]></flowable:string></flowable:field></flowable:taskListener></extensionElements></userTask>

其中,${抄送人员表达式}可以是一个变量,也可以是多个用户ID的拼接字符串(如"1,2,3"),或者是一个返回用户ID列表的表达式。

在监听器类中,我们使用private Expression userIds;来接收这个表达式。当监听器被触发时,userIds已经被流程引擎注入,我们可以通过userIds.getExpressionText()来获取表达式字符串,或者通过userIds.getValue(delegateTask)来获取表达式的值(如果表达式是变量,则会解析为变量值)。

但是,注意:在上面的代码中,监听器直接使用了userIds.getExpressionText(),这意味着它获取的是表达式原文,而不是解析后的值。如果表达式是变量,比如c c U s e r I d s ,那么这里得到的就是 " {ccUserIds},那么这里得到的就是"ccUserIds,那么这里得到的就是"{ccUserIds}"字符串,而不是变量值。因此,这种方式只适用于表达式是固定字符串的情况(比如直接写死用户ID列表)。

如果要支持表达式动态解析,应该使用userIds.getValue(delegateTask)来获取解析后的值。例如,如果我们在流程变量中设置了ccUserIds为"1,2,3",那么userIds.getValue(delegateTask)将返回一个字符串"1,2,3"。然后我们可以按照逗号分割得到用户ID列表。

注意:userIds.getValue(delegateTask)返回的类型是Object,我们需要根据实际情况进行类型转换。如果表达式是多个用户ID的列表,也可以配置为集合类型,但通常我们使用逗号分隔的字符串。

因此,修改后的代码可以这样写:

  • 如果表达式是固定字符串(比如"1,2,3"),那么使用getExpressionText()即可。

  • 如果表达式是变量,那么应该使用getValue(delegateTask)。

但是,为了同时支持两种情况,我们可以这样做:先尝试用getValue(delegateTask)获取值,如果返回值为空,则使用getExpressionText()。或者,我们可以在流程定义中明确配置是固定值还是变量。

然而,在大多数情况下,我们更希望支持动态变量。所以,我们可以这样修改:

@Slf4j@ComponentpublicclassFlowCopyTaskListenerimplementsTaskListener{/** * 抄送人员id集合 */privateExpressionuserIds;@Overridepublicvoidnotify(DelegateTaskdelegateTask){log.info("抄送任务监听器, 任务ID:{}",delegateTask.getId());// 获取抄送人员ID字符串StringuserIdsStr=null;if(userIds!=null){// 尝试获取表达式的值(如果是变量,则解析变量)Objectvalue=userIds.getValue(delegateTask);if(value!=null){userIdsStr=value.toString();}else{// 如果解析变量后为空,则使用表达式原文(可能是固定值)userIdsStr=userIds.getExpressionText();}}if(StringUtils.isNotBlank(userIdsStr)){// 从 Spring 容器中获取相关服务RepositoryServicerepositoryService=SpringUtils.getBean(RepositoryService.class);ISysCopyServicesysCopyService=SpringUtils.getBean(ISysCopyService.class);ISysUserServicesysUserService=SpringUtils.getBean(ISysUserService.class);// 获取流程定义信息ProcessDefinitionpd=repositoryService.createProcessDefinitionQuery().processDefinitionId(delegateTask.getProcessDefinitionId()).singleResult();// 获取任务发起人信息,注意:这里delegateTask.getAssignee()是当前任务的办理人,不一定是流程发起人// 如果需要流程发起人,可以通过流程实例的启动人获取// 例如:String startUserId = delegateTask.getProcessInstance().getStartUserId();// 这里根据业务需求调整Stringassignee=delegateTask.getAssignee();SysUserstartUser=null;if(assignee!=null){startUser=sysUserService.selectUserById(Long.parseLong(assignee));}// 构造 SysCopy 对象SysCopysysCopy=newSysCopy();sysCopy.setProcInsId(delegateTask.getProcessInstanceId());sysCopy.setDeployId(pd.getDeploymentId());sysCopy.setProcName(pd.getName());sysCopy.setTaskId(delegateTask.getId());sysCopy.setTaskName(delegateTask.getName());if(startUser!=null){sysCopy.setSendUserId(startUser.getUserId());sysCopy.setSendUserName(startUser.getNickName());}sysCopy.setStatus(0);sysCopy.setCreateTime(newDate());// 分割用户ID字符串,注意处理可能的分隔符(逗号、分号等)List<String>userIdList=Arrays.asList(userIdsStr.split("[,;]"));userIdList.forEach(userId->{// 去除空白userId=userId.trim();if(StringUtils.isNotBlank(userId)){sysCopy.setReceiveUserId(Long.parseLong(userId));// 插入抄送记录sysCopyService.insertSysCopy(sysCopy);}});}}}

注意:上面的代码中,我修改了获取流程发起人的方式。因为delegateTask.getAssignee()是当前任务的办理人,而抄送可能是由当前办理人发起的,所以这样设置。但是,如果抄送是要发送给其他人,而发起人应该是流程的启动人,那么就需要通过delegateTask.getProcessInstance().getStartUserId()来获取流程启动人。

另外,增加了sysCopy.setCreateTime(new Date());,因为通常插入记录需要创建时间。

还有,分割用户ID时,我支持了逗号和分号两种分隔符,并去除了空白。

这样修改后,监听器就可以支持表达式动态获取抄送人员列表了。

在流程定义中,我们可以这样配置:

固定值:1,2,3 变量:${ccUserIds},其中ccUserIds是流程变量,值为"1,2,3"

这样,监听器就可以灵活地获取抄送人员了。

八、完整的改进监听器示例

下面是一个灵活的监听器类的示例:

@Slf4j@ComponentpublicclassEnhancedFlowCopyTaskListenerimplementsTaskListener{@AutowiredprivateRepositoryServicerepositoryService;@AutowiredprivateISysCopyServicesysCopyService;@AutowiredprivateISysUserServicesysUserService;@AutowiredprivateApplicationContextapplicationContext;privateExpressionuserIds;privateExpressioncopyType;// 抄送类型:固定、变量、方法privateExpressioncopyRule;// 抄送规则@Overridepublicvoidnotify(DelegateTaskdelegateTask){try{// 1. 获取流程信息ProcessDefinitionpd=repositoryService.createProcessDefinitionQuery().processDefinitionId(delegateTask.getProcessDefinitionId()).singleResult();// 2. 解析抄送人员(支持多种方式)List<Long>userIds=resolveUserIds(delegateTask);// 3. 获取发起人(改进:从流程实例启动人获取)StringstartUserId=delegateTask.getProcessInstance().getStartUserId();SysUserstartUser=sysUserService.selectUserById(Long.parseLong(startUserId));// 4. 创建抄送记录createCopyRecords(delegateTask,pd,startUser,userIds);}catch(Exceptione){log.error("抄送任务处理失败",e);}}/** * 解析用户ID(支持多种表达式类型) */privateList<Long>resolveUserIds(DelegateTaskdelegateTask){// 获取表达式值Objectvalue=userIds.getValue(delegateTask.getExecution());if(value==null){returnCollections.emptyList();}// 根据类型处理if(valueinstanceofString){StringstrValue=(String)value;returnArrays.stream(strValue.split(",")).map(String::trim).filter(StringUtils::isNotBlank).map(Long::parseLong).collect(Collectors.toList());}elseif(valueinstanceofCollection){Collection<?>collection=(Collection<?>)value;returncollection.stream().filter(Objects::nonNull).map(obj->Long.parseLong(obj.toString())).collect(Collectors.toList());}returnCollections.emptyList();}}

在具体的代码中,直接使用 userIds.getExpressionText() 只适用于固定字符串的场景。如果要支持更复杂的场景(如从变量获取),应该使用 userIds.getValue(execution) 方法。


“人的一生会经历很多痛苦,但回头想想,都是传奇”。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 19:58:29

从零开始掌握Quartus II八位移位寄存器设计与仿真

从零开始掌握Quartus II八位移位寄存器设计与仿真 【免费下载链接】QuartusII八位移位寄存器仿真文件 本资源文件包含了一个在Quartus II软件中制作的八位移位寄存器电路设计。该电路设计使用了74ls74触发器&#xff0c;并通过Quartus II的电路仿真功能进行了仿真。通过波形仿真…

作者头像 李华
网站建设 2026/4/18 15:32:47

微信小程序任务管理终极指南:3分钟打造你的专属待办清单

微信小程序任务管理终极指南&#xff1a;3分钟打造你的专属待办清单 【免费下载链接】weapp-todos 一个简单的任务清单小程序, awesome weapp demo, todos, todolist 项目地址: https://gitcode.com/gh_mirrors/we/weapp-todos 还在为繁杂的日常任务感到焦虑吗&#xff…

作者头像 李华
网站建设 2026/4/19 15:11:16

KeyCastr终极指南:快速掌握macOS按键可视化技巧

KeyCastr终极指南&#xff1a;快速掌握macOS按键可视化技巧 【免费下载链接】keycastr KeyCastr, an open-source keystroke visualizer 项目地址: https://gitcode.com/gh_mirrors/ke/keycastr 在屏幕录制、技术演示或在线教学场景中&#xff0c;你是否曾苦恼观众无法看…

作者头像 李华
网站建设 2026/4/17 8:11:36

VideoSrt终极指南:15分钟从零掌握智能字幕制作全流程

VideoSrt终极指南&#xff1a;15分钟从零掌握智能字幕制作全流程 【免费下载链接】video-srt-windows 这是一个可以识别视频语音自动生成字幕SRT文件的开源 Windows-GUI 软件工具。 项目地址: https://gitcode.com/gh_mirrors/vi/video-srt-windows 你是否曾经为了给视频…

作者头像 李华
网站建设 2026/4/17 8:11:34

这行转网安,月薪翻倍+不用加班,我后悔没早转!

目录 1.前言2.开发的痛&#xff0c;谁懂&#xff1f; ① 需求变更多&#xff0c;代码改到吐② 加班没完没了&#xff0c;身体扛不住③ 技术更新太快&#xff0c;学不过来 3.转行网安所需技能 ①.代码能力&#xff0c;天然就是安全优势② 技能高度重叠&#xff0c;转型几乎零成…

作者头像 李华