当前位置: 首页 > news >正文

工作流引擎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) 方法。


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


http://www.cnnetsun.cn/news/22047.html

相关文章:

  • 《Python学习手册》第1章 Python概述
  • 西南民族大学软件工程25级研究生赴华清远见成都中心开启元宇宙实训之旅
  • Obsidian图像工具包:终极图片管理与编辑指南
  • 自主高性价比、高精度车规级姿态感知、倾角感知模组-应用消费级无人机、自动驾驶、机器人、智能制造、基础设施、智能穿戴等
  • ComfyUI智能修复技术:图像处理的革命性突破
  • 碳硅协同:人工智能作为碳基生命合作伙伴的终极形态分析
  • 小公司效率低、管理乱?一张《四维照妖镜》,照出你的“效率黑洞”
  • 拒绝无意义刷屏,打造高效率热点追踪,极空间部署『TrendRadar』
  • 3步精通JSON对比工具:从新手到高手的实战指南
  • lombok的几个核心注解是什么?
  • Qwen3-VL-30B-A3B-Thinking-FP8多模态大模型实战指南:从技术突破到产业落地
  • 庄散资金主买卖差、散买卖差
  • AI办公工具选型指南:从文档到PPT,这些工具如何提升效率?
  • Web 漏洞扫描入门没头绪?2025 十大工具(详细拆解),零基础也能从入门到精通!
  • Morisawa BIZ UDGothic 终极字体配置指南:提升文档专业度的免费利器
  • Markn:轻量级Markdown查看器的终极指南——提升文档阅读体验
  • 小白必看!大模型入门指南
  • 一篇图文彻底搞懂什么是AI Agent
  • Kubernetes备份工具API实战指南:从入门到精通
  • 18、Linux数据搜索、提取与归档全解析
  • 19、Linux 文件操作与编辑全解析
  • 日薪2000+的 “ 护网行动 ” 到底是什么?
  • 百度网盘秒传技术终极指南:零基础掌握极速文件传输
  • 2026年金融/咨询行业求职风向标:顶级简历模板权威榜单
  • 5大关键技巧彻底掌握AgentWeb:从基础配置到企业级实战
  • 轻松搞定视频下载:Seal让你的手机变身多媒体收藏库
  • FluidNC:ESP32运动控制的革命性解决方案
  • dify 导入工作流,会有些插件报错
  • 基于百度地图打造“美食地图”与3D路线规划
  • 【硬件新人指南】从零入门硬件行业:技能树、学习路径与职业规划