news 2026/4/16 16:03:57

工作流引擎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/16 15:51:08

《Python学习手册》第1章 课堂练习题

第1题 为什么 Python 能被称作“通用语言”? 1.1 需求 用 2–3 句话向完全不懂技术的产品经理解释“通用语言”这一评价。 1.2 举例 可对比 SQL(只能查数据库)、Shader(只能画图形)与 Python 在网站、AI、脚本三处的应用。 1.3 考点 对“通用”概念的理解;能举出跨…

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

颠覆传统动画开发:Lottie-Web让设计师与工程师完美协作

颠覆传统动画开发&#xff1a;Lottie-Web让设计师与工程师完美协作 【免费下载链接】lottie-web 项目地址: https://gitcode.com/gh_mirrors/lot/lottie-web 你是否曾经历过这样的困境&#xff1f;设计师在After Effects中创作出精美的动画效果&#xff0c;但工程师需要…

作者头像 李华
网站建设 2026/4/16 14:10:35

5分钟搭建音乐AI识别系统:Magenta零基础实战全攻略

5分钟搭建音乐AI识别系统&#xff1a;Magenta零基础实战全攻略 【免费下载链接】magenta Magenta: Music and Art Generation with Machine Intelligence 项目地址: https://gitcode.com/gh_mirrors/ma/magenta 想不想让AI帮你自动识别周杰伦的《七里香》和莫扎特的《小…

作者头像 李华
网站建设 2026/4/16 13:50:06

基于java Web 智慧食堂系统设计与实现

博主介绍&#xff1a;翰文编程 专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php phython node.js uniapp 微信小程序 等诸多技术领域和课设项目实战、企业信息化系统建设&#xff0c;从业十八余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆…

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

Story-Adapter终极指南:5分钟快速上手长故事可视化工具

Story-Adapter终极指南&#xff1a;5分钟快速上手长故事可视化工具 【免费下载链接】story-adapter A Training-free Iterative Framework for Long Story Visualization 项目地址: https://gitcode.com/gh_mirrors/st/story-adapter Story-Adapter是一个革命性的无训练…

作者头像 李华
网站建设 2026/4/12 16:22:38

FATE隐私计算框架:企业级数据安全协作终极指南

FATE隐私计算框架&#xff1a;企业级数据安全协作终极指南 【免费下载链接】FATE 项目地址: https://gitcode.com/gh_mirrors/fat/FATE 在数字经济时代&#xff0c;数据孤岛与隐私保护的矛盾日益突出。FATE&#xff08;Federated AI Technology Enabler&#xff09;作为…

作者头像 李华