本文为翻译文章,如有不合理处请查看原文https://hub.alfresco.com/t5/alfresco-process-services/activiti-7-deep-dive-series-using-the-core-libraries/ba-p/288484
Activiti 7是Alfresco经过实战考验的Activiti工作流引擎的演变,完全被采用在云环境中运行。 它是根据Cloud Native应用程序概念构建的,与之前的Activiti版本在架构方面有所不同。 我们在之前的文章中还有一个新的Activiti Modeler。在本文中,我们将使用新的Activiti 7 Process Runtime和Task Runtime Java API来试用Activiti 7流程引擎。 我们将从Spring Boot 2应用程序执行此操作。 我们需要的所有Activiti 7 Java工件都可以在Alfresco的Maven Repository(Nexus)中找到。Spring Boot应用程序还将包含Web组件(即Spring MVC),因此我们可以创建一个小的ReST API来用于启动进程以及与进程和任务交互。 Activiti 7提供了一个ReST API,但是当我们只使用核心库时,我们不打算在本节中使用它。 在这里,我们只创建自己的简单ReST API,它将使用Activiti 7 Java库(即Process Runtime和Task Runtime)。这个新的API旨在提供Cloud Native方法的明确途径。 它们还包括用户的安全和身份管理。新API还简化了一些常见用例。在本文中,我们将使用Activiti 7 Core库实际构建一个简单的业务流程管理(BPM)应用程序/解决方案。 这通常不是你要做的事情,但能够理解Activiti 7提供的API是一个很好的练习。
本文是详细介绍Activiti 7的系列文章的一部分,应按列出的顺序阅读。
您可以在此处找到与本文相关的源代码:
https://github.com/gravitonian/activiti7-api-basic-process
使用Spring Boot应用程序非常容易。 只需访问https://start.spring.io/并填写应用程序的数据,如下所示:
确保将Spring Boot版本2.0.x与Activiti 7 Beta 1 - 3一起使用,Beta 4应与版本2.1.x一致。
你不必像我一样使用相同的Group(org.activiti.training)和Artifact(activiti7-api-basic-process-usertask-servicetask-events)名称,只需使用您喜欢的任何名称即可。 但是,如果您从本文中复制代码,则使用相同的包名称(即同一组)可能会更容易。 搜索H2和Web依赖关系,以便它们包含在Maven POM中。 然后单击“Generate Project”按钮。 完成的Spring Boot 2 Maven项目将自动下载为ZIP。
在继续使用Activiti之前,让我们确保Spring Boot应用程序正常工作。 这涉及两个步骤。 首先构建应用程序JAR,然后运行应用程序JAR。
构建应用程序jar:
运行应用程序jar:
Ctrl-C退出应用程序以继续下面的配置。
除了Activiti 7依赖项之外,Spring Boot应用程序具有我们需要的大多数依赖项。 所以让我们添加它们。 我们可以使用BOM(物料清单)依赖关系,它将引入所有需要的Activiti 7依赖关系管理配置,包括所有依赖关系的正确版本。
Pom.xml添加以下内容:
这将导入Activiti 7的所有依赖项管理配置。现在我们只需要添加一个Activiti 7依赖项,它支持运行Activiti流程引擎,服务任务实现(如云连接器)和事件处理程序实现(如流程和任务侦听器)。 将以下依赖项添加到pom.xml:
这将带来所有Activiti和Spring依赖项需要运行嵌入在Spring Boot应用程序中的Activiti 7流程引擎。 我还可以编写我们的服务任务实现和我们的流程引擎事件处理程序。
我们还不能使用这些新的依赖项运行应用程序,因为它将在resources / processes目录中查找流程定义。 如果此目录不存在,则抛出异常并停止应用程序。
我们现在将我们在之前的一篇文章中设计的流程定义XML文件添加到项目中。 在src / main / resources目录下创建一个名为processes的新目录。 然后将.bpmn20.xml文件复制到此目录中。 您现在应该看到这样的目录结构:
这就是我们需要做的,我们现在可以测试启动应用程序了。
我们现在可以打包并运行应用程序,以查看所有Activiti库是否已正确加载和是否正确读取了流程定义而没有错误。
我们现在可以使用Activiti 7流程引擎运行时库运行应用程序,因此我们可以创建一些标准的Spring MVC rest调用来与流程引擎和可用的流程定义进行交互
为了能够与Process Runtime API进行交互,我们需要使用具有ROLE_ACTIVITI_USER角色的用户进行身份验证。 如果我们只是直接从Java代码调用Process Runtime API,例如来自带有main方法的类,那么我们可以在进行API调用之前直接设置用户上下文。注意。 这样做只是为了学习API而不是真正的生产实现......
我们将创建自己的小ReST API,因此我们希望使用Basic Auth通过Web浏览器进行身份验证。 Activiti使用Spring Security,因此我们可以很容易地做到这一点。在项目包中创建名为Activiti7ApplicationConfiguration的Spring配置类
@Configuration
@EnableWebSecurity
public class Activiti7ApplicationConfiguration extends WebSecurityConfigurerAdapter {
private Logger logger = LoggerFactory.getLogger(Activiti7ApplicationConfiguration.class);
@Override
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService());
}
@Bean
public UserDetailsService myUserDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
String[][] usersGroupsAndRoles = {
{"mbergljung", "1234", "ROLE_ACTIVITI_USER", "GROUP_activitiTraining"},
{"testuser", "1234", "ROLE_ACTIVITI_USER", "GROUP_activitiTraining"},
{"system", "1234", "ROLE_ACTIVITI_USER"},
{"admin", "1234", "ROLE_ACTIVITI_ADMIN"},
};
for (String[] user : usersGroupsAndRoles) {
List authoritiesStrings = Arrays.asList(Arrays.copyOfRange(user, 2, user.length));
logger.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]");
inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]),
authoritiesStrings.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList())));
}
return inMemoryUserDetailsManager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
设置我们在与流程引擎API交互时可以使用的一些用户和组。 我们在流程定义中使用testuser(分配给用户任务1),因此我们需要包含此用户。 我们还启用了Web安全性,因此我们可以构建一个使用Process Engine Java API的简单ReST API。
首先,让我们添加一个列出已部署流程定义的ReST调用。 在项目包中创建一个名为rest的新子包。 然后在这个新包中添加一个名为ProcessDefinitionsController的新Spring MVC Controller,如下所示:
/**
* ReST controller to interact with deployed process definitions
*
*/
@RestController
public class ProcessDefinitionsController {
private Logger logger = LoggerFactory.getLogger(ProcessDefinitionsController.class);
@Autowired
private ProcessRuntime processRuntime;
@GetMapping("/process-definitions")
public List getProcessDefinitions() {
Page processDefinitionPage = processRuntime.processDefinitions(Pageable.of(0, 10));
logger.info("> Available Process definitions: " + processDefinitionPage.getTotalItems());
for (ProcessDefinition pd : processDefinitionPage.getContent()) {
logger.info("\t > Process definition: " + pd);
}
return processDefinitionPage.getContent();
}
}
首先注入ProcessRuntime Spring bean,以便我们可以使用Process Runtime API,它具有以下方法:
在这种情况下,我们只需要processDefinitions()方法。 / process-definitions URL路径用于此ReST GET调用。
现在,按照前面的描述打包并运行应用程序。 启动应用程序时,您应该看到指示所有内容都已正确实现的日志:
转到浏览器并点击http:// localhost:8080 / process-definitions,您将看到一个登录对话框。 输入具有ROLE_ACTIVITI_USER角色的用户的用户名和密码,例如testuser / 1234。 然后你应该得到一个像这样的响应:
在日志中,你能看到以下内容:
我们现在知道我们可以使用一个流程定义来启动流程实例。 让我们创建一个可用于执行此操作的ReST调用。 它将使用流程定义键的一个参数。
在项目rest包中添加一个名为ProcessStartController的新Spring MVC Controller,如下所示:
@RestController
public class ProcessStartController {
private Logger logger = LoggerFactory.getLogger(ProcessStartController.class);
@Autowired
private ProcessRuntime processRuntime;
@RequestMapping("/start-process")
public ProcessInstance startProcess(
@RequestParam(value="processDefinitionKey", defaultValue="SampleProcess") String processDefinitionKey) {
ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder
.start()
.withProcessDefinitionKey(processDefinitionKey)
.withProcessInstanceName("Sample Process: " + new Date())
.withVariable("someProcessVar", "someProcVarValue")
.build());
logger.info(">>> Created Process Instance: " + processInstance);
return processInstance;
}
}
首先注入ProcessRuntime Spring bean,以便我们可以使用Process Runtime API,它具有我们需要的start()方法。 / start-process?processDefinitionKey = {processDefinitionKey} URL用于此ReST调用。
现在,按照前面的描述打包并运行应用程序。 启动应用程序时,您应该看到指示所有内容都已正确实现的日志:
转到浏览器并键入以下URL(使用与已部署的流程定义匹配的processDefinitionKey,你可以通过调用/ process-definitions来查看以前执行的内容):http:// localhost:8080 / start-process?processDefinitionKey = sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d,您应该看到一个登录对话框(如果缓存了凭据,则为NOT)。 为具有ROLE_ACTIVITI_USER角色的用户键入用户名和密码。 然后你应该得到一个像这样的响应:
在日志中,您应该看到以下内容:
能够列出活动流程实例很有用。 并且还能够获得有关流程实例的更多元数据,例如执行流程中的位置。 让我们为此创建几个ReST调用,这些调用可以派上用场。
在项目rest包中添加一个名为ProcessInstanceController的新Spring MVC Controller,如下所示:
/**
* ReST controller to get some info about a process instance
*
*/
@RestController
public class ProcessInstanceController {
private Logger logger = LoggerFactory.getLogger(ProcessInstanceController.class);
@Autowired
private ProcessRuntime processRuntime;
@GetMapping("/process-instances")
public List getProcessInstances() {
List processInstances =
processRuntime.processInstances(Pageable.of(0, 10)).getContent();
return processInstances;
}
@GetMapping("/process-instance-meta")
public ProcessInstanceMeta getProcessInstanceMeta(@RequestParam(value="processInstanceId") String processInstanceId) {
ProcessInstanceMeta processInstanceMeta = processRuntime.processInstanceMeta(processInstanceId);
return processInstanceMeta;
}
}
首先注入ProcessRuntime Spring bean,以便我们可以使用Process Runtime API,它具有我们需要的processInstances()和processInstanceMeta()方法。 / process-instances URL用于第一个只返回前10个活动流程实例的ReST调用。 第二个URL / process-instance-meta?processInstanceId = {processInstanceId}提供有关流程实例的信息,例如它当前正在等待的活动
现在,按照前面的描述打包并运行应用程序。 启动应用程序时,您应该看到指示所有内容都已正确实现的日志:
当我们运行内存数据库时,我们之前启动的流程实例将在重新启动后消失,创建一个新的http:// localhost:8080 / start-process?processDefinitionKey = sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d。
现在,要列出流程实例,请访问http:// localhost:8080 / process-instances URL。 您应该收到类似这样的回复:
然后,我们可以使用其他ReST调用查询此流程实例以获取更多信息。 键入以下URL(使用上面调用中返回的processInstanceId):http:// localhost:8080 / process-instance-meta?processInstanceId = b0a28a43-fa2b-11e8-9c34-acde48001122。 然后你应该得到一个像这样的响应:
我们的流程定义中的第一个活动是userTask1,因此在流程定义中执行的位置。 进程引擎正在等待以下用户任务完成:
当你遇到问题并且不确切知道流程实例在流程定义中等待的位置时,能够列出和检查流程实例非常有用。
随着流程的运行,我们应该能够列出可用任务,然后查看作为流程定义中第一个活动的用户任务。
在项目rest包中添加一个名为TaskManagementController的新Spring MVC Controller,如下所示:
@RestController
public class TaskManagementController {
private Logger logger = LoggerFactory.getLogger(TaskManagementController.class);
@Autowired
private TaskRuntime taskRuntime;
@GetMapping("/my-tasks")
public List getMyTasks() {
Page tasks = taskRuntime.tasks(Pageable.of(0, 10));
logger.info("> My Available Tasks: " + tasks.getTotalItems());
for (Task task : tasks.getContent()) {
logger.info("\t> My User Task: " + task);
}
return tasks.getContent();
}
}
首先注入TaskRuntime Spring bean,以便我们可以使用Task Runtime API。 此API将与当前用户相关的任务一起使用。 因此,当我们调用tasks()方法时,它将返回分配给当前登录用户的任务。 API具有以下方法:
在这种情况下,我们只需要tasks()方法。 / my-tasks URL路径用于此ReST GET调用。
当我们想要查看在所有活动流程实例中分配的所有任务时,我们还可以添加ReST调用。 可用于支持和管理目的,例如当您想要代表某人重新分配任务或完成任务时。 此调用将需要管理员凭据(即我们需要以具有ROLE_ACTIVITI_ADMIN的用户身份登录)。 我们还需要使用名为TaskAdminRuntime的不同运行时API。 这是方法:
@Autowired
private TaskAdminRuntime taskAdminRuntime;
...
@GetMapping("/all-tasks")
public List getAllTasks() {
Page tasks = taskAdminRuntime.tasks(Pageable.of(0, 10));
logger.info("> All Available Tasks: " + tasks.getTotalItems());
for (Task task : tasks.getContent()) {
logger.info("\t> User Task: " + task);
}
return tasks.getContent();
}
现在,按照前面的描述打包并运行应用程序。 启动应用程序时,您应该看到指示所有内容都已正确实现的日志:
我们需要重新启动之前内存中运行的流程实例,创建一个新的http:// localhost:8080 / start-process?processDefinitionKey = sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d
我启动了以用户mbergljung身份登录的流程实例。 因为用户任务被分配给用户testuser,所以当我们调用taskRuntime.tasks()时它不会显示。 在继续下面的ReST调用之前,我们必须以testuser身份注销并再次登录(最简单的方法是在下次ReST调用之前清除浏览器缓存)
然后键入以下URL:http:// localhost:8080 / my-tasks,然后您应该得到如下所示的响应:
在日志中,您应该看到以下内容:
现在,通过清除浏览器缓存注销,然后点击http:// localhost:8080 / all-tasks URL。 当要求登录时使用admin / 1234凭据。 作为响应,您应该看到相同的用户任务。
我们现在处于一个阶段,我们应该能够实现一个ReST调用,该调用可用于把我们刚刚列出的用户任务完成分配给testuser。在我们刚刚使用的同一个控制器中,称为TaskManagementController,实现以下ReST调用:
@RequestMapping("/complete-task")
public String completeTask(@RequestParam(value="taskId") String taskId) {
taskRuntime.complete(TaskPayloadBuilder.complete()
.withTaskId(taskId).build());
logger.info(">>> Completed Task: " + taskId);
return "Completed Task: " + taskId;
}
这里我们使用TaskRuntime API的complete()方法。 我们需要与分配了任务的用户(所以testuser)一起登录才能完成它。 / complete-task?taskId = {taskId} URL路径用于此ReST GET调用。
如前所述,包装并运行应用程序。 启动应用程序时,您应该看到指示所有内容都已正确实现的日志:
我们需要重新启动之前内存中运行的流程实例,创建一个新的http:// localhost:8080 / start-process?processDefinitionKey = sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d,我启动了以用户mbergljung身份登录的流程实例。 因为用户任务被分配给用户testuser,所以当我们调用taskRuntime.tasks()时它不会显示,我将无法完成它。在继续下面的ReST调用之前,我们必须以testuser身份注销并再次登录(最简单的方法是在下次ReST调用之前清除浏览器缓存)。然后键入以下URL:http:// localhost:8080 / my-tasks,然后您应该得到如下所示的响应:
记下任务ID,然后在http:// localhost:8080 / complete-task?taskId = b0a60cb6-fa2b-11e8-9c34-acde48001122调用中使用它来完成任务。 这将使流程实例转换到下一个活动,在我们的例子中是一个服务任务。由于我们尚未实现服务任务,因此我们将在日志和浏览器中看到以下异常:
所以让我们修复服务任务实现
Activiti 7中的服务任务和侦听器的实现方式与以前的版本不同。
服务任务是我们流程定义中的最后一个活动。 让我们实现它,以便我们可以完成流程实例。
我们需要做的是创建一个名为serviceTask1Impl的Spring Bean,它将代表服务任务的实现。 Spring bean需要是org.activiti.runtime.api.connector.Connector类型接口。 这个新的Connector接口是Java Delegates的自然演变,Activiti 7 Core将尝试通过将它们包装在Connector实现中来重用Java Delegates
在项目包中创建一个名为connectors的新子包。 然后在这个新包中添加一个名为ServiceTask1Connector的新Spring bean,如下所示:
@Service(value = "serviceTask1Impl")
public class ServiceTask1Connector implements Connector {
private Logger logger = LoggerFactory.getLogger(ServiceTask1Connector.class);
public IntegrationContext execute(IntegrationContext integrationContext) {
logger.info("Some service task logic... [processInstanceId=" + integrationContext.getProcessInstanceId() + "]");
return integrationContext;
}
}
连接器将一个Bean名称自动注入到ProcessRuntime,在此示例中为“serviceTask1Impl”。 这个bean名称是从我们的流程定义中的serviceTask元素的implementation属性中获取的:
<bpmn2:serviceTask id="ServiceTask_1wg38me" name="Service Task 1" implementation="serviceTask1Impl">
连接器接收具有流程实例信息和流程变量的IntegrationContext,并返回修改后的IntegrationContext,其中包含需要映射回流程变量的结果。
现在,按照前面的描述打包并运行应用程序。我们需要重新启动之前内存中运行的流程实例,创建一个新的http:// localhost:8080 / start-process?processDefinitionKey = sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d。 以用户testuser身份登录,以便我们可以始终保持相同的用户。
然后键入以下URL:http:// localhost:8080 / my-tasks,然后您应该得到如下所示的响应:
记下在http:// localhost:8080 / complete-task?taskId = 79ce7445-fc4b-11e8-95e6-acde48001122调用中使用它来完成任务的ID。 这将使流程实例转换到下一个活动,在我们的例子中是一个服务任务。 您应该在日志中看到以下内容:
现在检查流程实例是否已完成。 我们可以使用之前开发的http:// localhost:8080 / process-instances调用来实现。 它应该返回一个空列表。
进程监听器和任务监听器传统上是Activiti中专门实现扩展。 这些扩展还意味着代码将与流程执行同步运行。这在云部署环境中是不利的。在Activiti 7中流程引擎会以异步方式监听和订阅的发出事件。
通过实现org.activiti.api.process.runtime.events.listener.ProcessRuntimeEventListener接口创建进程监听器。
在项目包中创建一个名为listeners的新子包。然后在这个新包中添加一个名为MyProcessEventListener的新Spring bean,如下所示:
@Service
public class MyProcessEventListener implements ProcessRuntimeEventListener {
private Logger logger = LoggerFactory.getLogger(MyProcessEventListener.class);
@Override
public void onEvent(RuntimeEvent runtimeEvent) {
if (runtimeEvent instanceof ProcessStartedEvent)
logger.info("Do something, process is started: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessCompletedEvent)
logger.info("Do something, process is completed: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessCancelledEvent)
logger.info("Do something, process is cancelled: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessSuspendedEvent)
logger.info("Do something, process is suspended: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessResumedEvent)
logger.info("Do something, process is resumed: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessCreatedEvent)
logger.info("Do something, process is created: " + runtimeEvent.toString());
else if (runtimeEvent instanceof SequenceFlowTakenEvent)
logger.info("Do something, sequence flow is taken: " + runtimeEvent.toString());
else if (runtimeEvent instanceof VariableCreatedEvent)
logger.info("Do something, variable was created: " + runtimeEvent.toString());
else
logger.info("Unknown event: " + runtimeEvent.toString());
}
}
通过实现ProcessRuntimeEventListener接口,进程侦听器自动连接到ProcessRuntime。 监听器收到一个RuntimeEvent,其中包含有关该事件的所有信息。 我们可以查看子类来确定事件的内容,例如ProcessCompletedEvent。
以同样的方式,我们可以通过实现org.activiti.api.task.runtime.events.listener.TaskRuntimeEventListener接口来创建任务侦听器。在这个新包中添加一个名为MyTaskEventListener的新Spring bean,如下所示:
@Service
public class MyTaskEventListener implements TaskRuntimeEventListener {
private Logger logger = LoggerFactory.getLogger(MyTaskEventListener.class);
@Override
public void onEvent(RuntimeEvent runtimeEvent) {
if (runtimeEvent instanceof TaskActivatedEvent)
logger.info("Do something, task is activated: " + runtimeEvent.toString());
else if (runtimeEvent instanceof TaskAssignedEvent) {
TaskAssignedEvent taskEvent = (TaskAssignedEvent)runtimeEvent;
Task task = taskEvent.getEntity();
logger.info("Do something, task is assigned: " + task.toString());
} else if (runtimeEvent instanceof TaskCancelledEvent)
logger.info("Do something, task is cancelled: " + runtimeEvent.toString());
else if (runtimeEvent instanceof TaskCompletedEvent)
logger.info("Do something, task is completed: " + runtimeEvent.toString());
else if (runtimeEvent instanceof TaskCreatedEvent)
logger.info("Do something, task is created: " + runtimeEvent.toString());
else if (runtimeEvent instanceof TaskSuspendedEvent)
logger.info("Do something, task is suspended: " + runtimeEvent.toString());
else
logger.info("Unknown event: " + runtimeEvent.toString());
}
}
现在,按照前面的描述打包并运行应用程序。 使用http:// localhost:8080 / start-process? processDefinitionKey = sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d创建一个新的实例,这时候我们可以看到很多事件记录: