news 2026/2/22 8:27: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/2/22 8:58:53

终极Mac观影神器:打造你的私人美剧影院

终极Mac观影神器&#xff1a;打造你的私人美剧影院 【免费下载链接】iMeiJu_Mac 爱美剧Mac客户端 项目地址: https://gitcode.com/gh_mirrors/im/iMeiJu_Mac 还在为找美剧资源而烦恼吗&#xff1f;在各大视频平台间频繁切换&#xff0c;只为找到心仪的那一部&#xff1f…

作者头像 李华
网站建设 2026/2/19 15:25:27

Linux下通过命令行实现防火墙操作

在Linux系统上管理防火墙&#xff0c;ufw (Uncomplicated Firewall) 是一个非常流行且易于使用的工具&#xff0c;它是 iptables 的一个前端。 &#x1f527; UFW的安装 ufw 通常预装在基于Debian的系统&#xff08;如Ubuntu&#xff09;上。如果你的系统没有&#xff0c;可以使…

作者头像 李华
网站建设 2026/2/16 20:16:36

测试数据管理的自动化工具

被忽视的质量基石 在敏捷开发与持续交付成为主流的今天&#xff0c;测试数据管理&#xff08;TDM&#xff09;仍存在明显滞后性。据行业调研显示&#xff0c;超过67%的软件缺陷源于测试数据问题——数据污染、覆盖不全、环境差异等痛点直接拖累交付周期。本文通过解构自动化工…

作者头像 李华
网站建设 2026/2/21 16:05:40

智能测试数据脱敏技术:保障数据安全与测试效率的工程实践

测试数据管理的困境与破局 在敏捷开发与DevOps普及的今天&#xff0c;软件测试活动日趋频繁。传统的测试数据准备方式——无论是直接使用生产数据的“裸奔”行为&#xff0c;还是耗费大量人力手动编写模拟数据的“作坊”模式——都已无法满足现代软件工程对效率、安全与质量的…

作者头像 李华
网站建设 2026/2/20 15:56:09

MTK设备bootrom保护绕过技术详解:专业级安全解锁方案

MTK设备bootrom保护绕过技术详解&#xff1a;专业级安全解锁方案 【免费下载链接】bypass_utility 项目地址: https://gitcode.com/gh_mirrors/by/bypass_utility MTK芯片设备的安全保护机制一直是手机刷机和系统定制的重要障碍。本工具通过技术手段实现对bootrom保护的…

作者头像 李华