脚本任务可能是Activiti代码库中“最古老的”类之一,但我认为它仍然未被许多人使用。 (可以理解的?)缺点当然是性能(解释还是编译),并且从IDE角度来看支持较少。
但是,好处(imo)超过了这一点:
- 脚本在流程xml本身中定义。 无需担心版本控制和类路径上的库问题。
- 过去我们看到的是,技术水平较低的人不敢尝试脚本。 但是从来没有Java。
无论如何,很少有人知道或已经意识到的是,您可以在Activiti中的脚本中做得很棒,并可以进行一些高级工作。 由于此类脚本是在流程引擎中执行的,因此您可以访问该引擎的所有功能。 是的...一切...这使其既非常强大,又(潜在)危险(如果您不知道自己在做什么)。
让我为您讲解这样的例子。 我喜欢将其称为“定制配置注入”的概念 ,因为它有效地使您可以在运行时添加定制逻辑,从而显着改变流程的执行。 如果您的名字更酷,请告诉我。
所有代码都可以在我的Github页面上找到: https : //github.com/jbarrez/activiti-advanced-scripting
用例
现在我想做什么。 好吧,我想有一个流程,执行时
- 向执行的每个用户任务添加“任务完成事件处理程序”
- 此事件处理程序必须向远程URL触发自定义事件,在该URL上事件处理器可能正在执行其工作
因此,基本上,我们希望在任务完成时将自定义事件触发到某个远程URL。 一个很好的用例是商业智能报告/复杂事件处理,例如使用Esper之类的东西。
第一个版本
可以在https://github.com/jbarrez/activit-advanced-scripting/blob/master/src/test/resources/org/activiti/test/my-process.bpmn20.xml中找到该功能的第一版 。 执行此过程时,将发生以下情况:
var config = Context.getProcessEngineConfiguration();
var bpmnParser = config.getBpmnParser();
我们仅获取当前的ProcessEngineConfiguration实例。 我们将从此配置中获取BpmnParser实例,因为我们想更改整个引擎的常规用户任务解析。
接下来,我们构建脚本:
var script = "";
script = script + "importPackage(java.net);";
script = script + "importPackage(java.io);";
script = script + "var url = new URL('http://localhost:8182/echo');";
script = script + "var connection = url.openConnection();";
script = script + "connection.setRequestMethod('POST');";
script = script + "connection.setDoOutput(true);";
script = script + "var outputStream = new BufferedOutputStream(connection.getOutputStream());";
script = script + "outputStream.write(new java.lang.String(\"{'eventType':'task-complete'}\").bytes);";
script = script + "outputStream.flush();";
script = script + "connection.connect();";
script = script + "var respCode = connection.getResponseCode();";
script = script + "if (respCode != 200) ";
script = script + "println('Response code : ' + respCode);";
script = script + "outputStream.close();";
script = script + "connection.disconnect();";
显然,这不是执行此操作的最有效方法,但可以肯定地说明了发生的情况。 消息'eventType:task-complete'通过标准java.net和java.io类发送到localhost:8182 url。
接下来是棘手的部分:
var handler = new ExecuteScriptOnTaskCompleteBpmnParseHandler("JavaScript");
handler.setUserTaskCompleteScript(script);
bpmnParser.getBpmnParserHandlers().addHandler(handler);// reset the deployment cache such that the new listener gets picked up on a new redeploy
config.getProcessDefinitionCache().clear();
在这里,我们将BpmnParseHandler类添加到引擎配置中。 解析处理程序会将上面定义的脚本的执行添加到引擎发出的“任务完成事件”的每次接收中。 每次对用户任务进行解析时,该解析处理程序都会启动,从而有效地将我们的“将事件发送到远程服务”添加到您的Activiti环境中现在发生的每个用户任务中!
有一个单元测试以了解其工作原理: https : //github.com/jbarrez/activiti-advanced-scripting/blob/master/src/test/java/org/activiti/test/ExecuteScriptInProcessTest.java 。 在测试中,我们设置了一个非常简单的“回显服务”,只要接收到这样的事件,它就会简单地打印出来。 如果在IDE中运行它,您将看到类似以下内容:
但是我们可以做得更好
但是我们可以做得更好。 检查以下代码。
var handler = new ExecuteScriptOnTaskCompleteBpmnParseHandler("JavaScript");
handler.setUserTaskCompleteScript("http://localhost:8182/scripts/task-complete.js");
handler.setExecuteScriptInJob(true);
bpmnParser.getBpmnParserHandlers().addHandler(handler);// Update the configuration to use the correct job handler
var jobHandler = new ExecuteScriptJobHandler();
config.getJobHandlers().put(jobHandler.type,jobHandler);
此代码与上一节中的代码相同。 将“完成”事件的侦听器附加到每个用户任务。 但是,此实现:
- 异步执行脚本
- 没有在流程xml中定义脚本,但是它是从远程URL获取的
- 更新作业处理程序配置
如果您问我,那太棒了! 因此,这意味着向远程服务实际发送消息不会影响流程实例的执行性能 。 显然,从这里您可以发疯,添加持久队列和所有这些幻想。 最重要的是,脚本始终是从远程服务器获取的。 如果要更新执行的逻辑,只需更改返回的脚本。 这意味着您可以在不影响实际流程的情况下影响运行时的流程执行。
在https://github.com/jbarrez/activiti-advanced-scripting/blob/master/src/test/java/org/activiti/test/ExecuteScriptWithJobTest.java有一个单元测试
如果运行此测试,则会看到以下内容。 请注意,我们在测试服务器上将完成脚本托管为名为“ task-complete.js”的静态文件。
在测试中,您可以看到我们必须专门执行异步作业才能查看测试的输出。
警告
需要注意的是:当流程引擎重新启动时,将从配置文件中重新加载配置。 因此,不添加从上方插入自定义逻辑的过程。 但是,这可以通过使用ProcessEngineLifeCycleListener实现轻松完成,该实现在引擎启动后执行特定类别的流程定义。 例如,如果将所有这些进程的“ config-processes”都设为类别,则可以轻松地在启动时执行它们。
结论
BPMN 2.0流程中的脚本编写是一项非常强大的功能。 它使您可以在几行之内更改整个引擎的流程执行。 当然,以上所有代码都可以使用Java完成。 但是以上示例仅使用标准BPMN 2.0和每次JDK安装中捆绑的javascript引擎。
谢谢阅读。 编码愉快!
翻译自: https://www.javacodegeeks.com/2013/07/advanced-scripting-in-activiti-custom-configuration-injection.html