Activiti中ScriptTaskListener复用脚本代码的报错及方案咨询
首先,我们先明确你遇到的核心问题根源:
你误以为activiti:expression会先求值得到流程变量taskScript中的脚本字符串,再传递给ScriptTaskListener执行,但实际上,Activiti的ScriptTaskListener对script字段的activiti:expression处理逻辑和你预期的完全不同——它会把表达式本身(也就是${taskScript})直接当作Groovy脚本代码去执行。而Groovy中的${}是GString语法,会尝试调用不存在的$()方法,这就是你看到MissingMethodException的原因。
接下来,给你两种可行的脚本复用方案:
方案1:使用外部脚本文件(推荐)
这是最简洁也最易维护的方式:把通用的Groovy脚本单独存放在classpath下的文件中,所有需要复用的taskListener都引用这个文件。
步骤1:创建外部脚本文件
在项目的src/main/resources目录下新建scripts/reusable-task-logger.groovy,内容如下:
def log = org.slf4j.LoggerFactory.getLogger("my.Logger"); log.info("works"); // 这里可以添加更多通用的任务处理逻辑
步骤2:在BPMN中引用脚本文件
修改你的taskListener配置,用activiti:resource指向外部脚本文件:
<activiti:taskListener event="complete" class="org.activiti.engine.impl.bpmn.listener.ScriptTaskListener" > <activiti:field name="script"> <activiti:resource>classpath:scripts/reusable-task-logger.groovy</activiti:resource> </activiti:field> <activiti:field name="language" stringValue="groovy" /> </activiti:taskListener>
这样,所有需要复用这段逻辑的地方都可以直接引用这个文件,后续修改脚本只需更新这一个文件即可,还能避免流程变量存储脚本带来的安全风险。
方案2:自定义TaskListener复用流程变量中的脚本
如果你必须通过流程变量来存储和复用脚本,可以自定义一个TaskListener类,手动处理脚本的读取和执行:
步骤1:编写自定义Listener类
import org.activiti.engine.delegate.DelegateTask; import org.activiti.engine.delegate.TaskListener; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; public class ReusableScriptTaskListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { // 从流程变量中获取预定义的脚本内容 String scriptContent = (String) delegateTask.getVariable("taskScript"); if (scriptContent == null || scriptContent.isBlank()) { throw new IllegalArgumentException("流程变量'taskScript'不存在或为空"); } // 初始化Groovy脚本引擎 ScriptEngine groovyEngine = new ScriptEngineManager().getEngineByName("groovy"); // 将任务和执行上下文绑定到脚本,方便脚本中直接使用 groovyEngine.put("task", delegateTask); groovyEngine.put("execution", delegateTask.getExecution()); try { // 执行脚本 groovyEngine.eval(scriptContent); } catch (Exception e) { throw new RuntimeException("执行复用脚本失败", e); } } }
步骤2:在BPMN中配置自定义Listener
把原来的ScriptTaskListener替换成你自定义的类:
<activiti:taskListener event="complete" class="com.yourcompany.activiti.listener.ReusableScriptTaskListener" />
当任务触发complete事件时,这个自定义Listener会自动读取流程变量taskScript中的脚本并执行,完全满足你复用脚本的需求。
补充说明
为什么原来的activiti:expression方式无法工作?
Activiti的ScriptTaskListener设计时,script字段的activiti:expression是用来让脚本代码中包含UEL表达式(比如在脚本里引用${task.id}这类变量),而不是让表达式求值后得到脚本代码。所以它会直接把表达式字符串作为脚本交给Groovy引擎,而不是先解析表达式得到流程变量的值。
内容的提问来源于stack exchange,提问作者dschulten

