Java工作流引擎Activiti/Camunda使用

引言:工作流引擎的崛起

在当今数字化转型的大潮中,企业对自动化和效率提升的需求日益增长。传统的业务流程管理(BPM)工具虽然能够帮助企业在一定程度上实现流程的标准化和优化,但在面对复杂多变的业务需求时,往往显得力不从心。随着云计算、微服务架构和DevOps等技术的兴起,越来越多的企业开始寻求更加灵活、高效的工作流解决方案。正是在这种背景下,Java工作流引擎如Activiti和Camunda应运而生。

Activiti和Camunda都是基于Java开发的开源工作流引擎,它们为企业提供了强大的流程建模、执行和监控功能。与传统的BPM工具不同,这两款引擎不仅支持图形化的流程设计,还允许开发者通过代码直接操作流程,极大地提升了灵活性和可扩展性。更重要的是,它们与现代微服务架构完美契合,能够在分布式环境中轻松部署和管理。

本文将以轻松诙谐的方式,深入探讨Activiti和Camunda的核心特性、使用场景以及最佳实践。我们将通过大量的代码示例和表格,帮助读者更好地理解和掌握这两款引擎的使用方法。无论你是初学者还是有经验的开发者,相信这篇文章都能为你带来新的启发和收获。

Activiti vs. Camunda:一场友好的对决

在Java工作流引擎的世界里,Activiti和Camunda无疑是两颗璀璨的明星。它们都源自同一血脉——Activiti最早由Alfresco公司开发,后来演变成了独立的开源项目。而Camunda则是在Activiti的基础上进行了深度优化和重构,逐渐形成了自己独特的技术路线。那么,这两款引擎究竟有哪些异同呢?让我们来一探究竟。

1. 历史渊源

  • Activiti:Activiti诞生于2010年,最初是Alfresco公司为了满足其内容管理系统中的流程需求而开发的。它采用了Apache License 2.0协议,迅速吸引了大量开发者和企业的关注。Activiti的设计理念是“轻量级、高性能”,旨在为开发者提供一个简单易用的工作流引擎。然而,随着时间的推移,Activiti的社区活跃度逐渐下降,官方团队也减少了对项目的维护和支持。

  • Camunda:Camunda成立于2013年,由一群对Activiti充满热情的开发者创立。他们认为Activiti虽然功能强大,但在某些方面还有改进的空间。因此,Camunda团队在保留Activiti核心优势的基础上,对其进行了大规模的优化和扩展。Camunda不仅修复了Activiti中的一些Bug,还引入了许多新的特性和工具,如Web应用程序、REST API、任务管理器等。如今,Camunda已经成为全球最受欢迎的开源工作流引擎之一。

2. 架构设计

  • Activiti:Activiti采用了经典的三层架构,分别为持久层、核心层和API层。持久层负责与数据库交互,存储流程定义、实例和历史记录;核心层实现了流程引擎的核心逻辑,包括流程解析、执行和事件处理;API层则为开发者提供了丰富的接口,方便他们在应用中集成和操作流程。Activiti的架构设计相对简洁,适合中小型项目快速上手。

  • Camunda:Camunda的架构设计更为模块化和灵活。它将流程引擎拆分为多个独立的模块,每个模块都可以根据需要进行定制和扩展。例如,Camunda提供了独立的任务管理器、历史记录模块、权限控制模块等。这种模块化的设计使得Camunda在大型项目中具有更强的可维护性和扩展性。此外,Camunda还支持多种部署方式,包括单机、集群和云原生部署,适应了不同的应用场景。

3. 功能对比

功能 Activiti Camunda
流程建模工具 提供了简单的Web建模工具 提供了功能更强大的Web建模工具(Modeler)
流程执行 支持BPMN 2.0标准 支持BPMN 2.0标准,增加了CMMN和DMN
任务管理 提供基本的任务管理和分配功能 提供了更完善的任务管理功能,支持任务池和优先级
历史记录 记录流程实例和任务的历史信息 提供了更详细的历史记录,支持自定义字段
REST API 提供了基本的REST API 提供了更完善的REST API,支持更多操作
集成能力 支持Spring、JPA等框架的集成 支持Spring Boot、Spring Cloud等现代框架的集成
性能优化 性能表现良好,但缺乏大规模测试 经过大规模性能测试,支持高并发和大数据量

4. 社区与支持

  • Activiti:尽管Activiti曾经是开源工作流引擎领域的佼佼者,但近年来其社区活跃度有所下降。官方团队已经不再频繁发布新版本,主要依赖社区贡献者的维护。这导致了一些问题无法得到及时解决,尤其是在面对复杂的生产环境时,用户可能会遇到一些挑战。

  • Camunda:相比之下,Camunda的社区非常活跃,拥有大量的开发者和企业用户。Camunda不仅提供了详细的官方文档和技术支持,还定期举办线上线下的技术交流活动。此外,Camunda还推出了商业版产品,为企业用户提供更专业的服务和支持。

5. 使用场景

  • Activiti:由于其轻量级的特点,Activiti非常适合中小型企业和初创公司使用。如果你的应用场景相对简单,流程数量较少,Activiti可以满足你的需求。此外,Activiti的API设计非常友好,适合那些希望快速集成工作流功能的开发者。

  • Camunda:对于大型企业和复杂的应用场景,Camunda无疑是更好的选择。它不仅支持更多的流程标准(如CMMN和DMN),还提供了丰富的工具和插件,帮助企业更好地管理和优化业务流程。如果你的应用需要处理大量的并发请求或涉及复杂的业务逻辑,Camunda的强大性能和扩展性将为你带来极大的便利。

搭建你的第一个工作流:Activiti入门

既然我们已经了解了Activiti和Camunda的基本情况,接下来就让我们动手搭建一个简单的工作流吧!我们将以Activiti为例,展示如何从零开始创建一个完整的流程,并通过代码实现流程的启动和执行。

1. 环境准备

首先,我们需要准备好开发环境。假设你已经安装了Java开发工具包(JDK)和Maven构建工具,接下来按照以下步骤进行配置:

  1. 创建Maven项目:打开命令行工具,进入你想要创建项目的目录,执行以下命令:

    mvn archetype:generate -DgroupId=com.example -DartifactId=activiti-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
  2. 添加Activiti依赖:打开pom.xml文件,添加以下依赖项:

    <dependencies>
     <dependency>
       <groupId>org.activiti</groupId>
       <artifactId>activiti-engine</artifactId>
       <version>7.1.0.M6</version>
     </dependency>
     <dependency>
       <groupId>com.h2database</groupId>
       <artifactId>h2</artifactId>
       <version>1.4.200</version>
       <scope>runtime</scope>
     </dependency>
    </dependencies>
  3. 配置数据库连接:在src/main/resources目录下创建一个名为activiti.cfg.xml的文件,用于配置Activiti的数据库连接。你可以使用H2内存数据库进行本地测试:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
     <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
       <property name="jdbcUrl" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=-1"/>
       <property name="jdbcDriver" value="org.h2.Driver"/>
       <property name="jdbcUsername" value="sa"/>
       <property name="jdbcPassword" value=""/>
       <property name="databaseSchemaUpdate" value="true"/>
     </bean>
    
    </beans>

2. 创建流程定义

接下来,我们需要定义一个简单的业务流程。Activiti支持BPMN 2.0标准,因此我们可以使用BPMN文件来描述流程。在src/main/resources目录下创建一个名为hello.bpmn20.xml的文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             targetNamespace="http://www.example.org/hello">

  <process id="helloProcess" name="Hello World Process">
    <startEvent id="startEvent" name="Start"/>
    <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="userTask"/>

    <userTask id="userTask" name="Say Hello" activiti:assignee="john"/>
    <sequenceFlow id="flow2" sourceRef="userTask" targetRef="endEvent"/>

    <endEvent id="endEvent" name="End"/>
  </process>

</definitions>

这个流程非常简单,包含三个节点:开始事件、用户任务和结束事件。用户任务被分配给了名为john的用户,表示他需要完成某个操作(例如填写表单或审批申请)。

3. 启动流程引擎

现在,我们已经定义好了流程,接下来需要编写代码来启动Activiti引擎并部署流程。在src/main/java/com/example目录下创建一个名为ActivitiDemo.java的类,代码如下:

package com.example;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;

public class ActivitiDemo {

  public static void main(String[] args) {
    // 获取默认的流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    // 获取RepositoryService对象,用于管理流程定义
    RepositoryService repositoryService = processEngine.getRepositoryService();

    // 部署流程定义
    Deployment deployment = repositoryService.createDeployment()
        .addClasspathResource("hello.bpmn20.xml")
        .deploy();

    System.out.println("流程已部署,部署ID:" + deployment.getId());

    // 获取RuntimeService对象,用于启动流程实例
    RuntimeService runtimeService = processEngine.getRuntimeService();

    // 启动流程实例
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("helloProcess");

    System.out.println("流程已启动,实例ID:" + processInstance.getId());

    // 获取TaskService对象,用于管理任务
    TaskService taskService = processEngine.getTaskService();

    // 查询当前待办任务
    Task task = taskService.createTaskQuery().singleResult();

    System.out.println("当前任务:" + task.getName() + ",分配给:" + task.getAssignee());

    // 完成任务
    taskService.complete(task.getId());

    System.out.println("任务已完成,流程结束!");
  }
}

这段代码的主要逻辑如下:

  1. 获取默认的流程引擎实例。
  2. 使用RepositoryService部署BPMN文件,将其加载到引擎中。
  3. 使用RuntimeService启动流程实例,指定要启动的流程定义键(helloProcess)。
  4. 使用TaskService查询当前待办任务,并模拟用户完成任务。
  5. 最后,输出流程的执行结果。

4. 运行程序

一切准备就绪后,我们可以在命令行中运行程序:

mvn clean compile exec:java -Dexec.mainClass="com.example.ActivitiDemo"

如果一切顺利,你应该会看到如下输出:

流程已部署,部署ID:1
流程已启动,实例ID:1
当前任务:Say Hello,分配给:john
任务已完成,流程结束!

恭喜你,你已经成功搭建了一个基于Activiti的工作流!接下来,我们可以通过修改BPMN文件和代码,进一步扩展和完善这个流程。例如,你可以添加更多的任务节点、条件分支、子流程等,以满足实际业务需求。

Camunda进阶:构建复杂的工作流

在掌握了Activiti的基础之后,让我们一起探索Camunda的更多高级功能。Camunda不仅继承了Activiti的优点,还在许多方面进行了创新和优化。接下来,我们将通过一个更复杂的案例,展示如何使用Camunda构建一个多阶段的业务流程。

1. 项目初始化

与Activiti类似,我们首先需要创建一个Maven项目,并添加Camunda的相关依赖。打开pom.xml文件,添加以下依赖项:

<dependencies>
  <dependency>
    <groupId>org.camunda.bpm</groupId>
    <artifactId>camunda-engine</artifactId>
    <version>7.16.0</version>
  </dependency>
  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.200</version>
    <scope>runtime</scope>
  </dependency>
</dependencies>

接着,配置数据库连接。在src/main/resources目录下创建一个名为camunda.cfg.xml的文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="processEngineConfiguration" class="org.camunda.bpm.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">
    <property name="jdbcUrl" value="jdbc:h2:mem:camunda;DB_CLOSE_DELAY=-1"/>
    <property name="jdbcDriver" value="org.h2.Driver"/>
    <property name="jdbcUsername" value="sa"/>
    <property name="jdbcPassword" value=""/>
    <property name="databaseSchemaUpdate" value="true"/>
  </bean>

</beans>

2. 设计复杂流程

为了展示Camunda的更多功能,我们将设计一个包含多个阶段的业务流程。假设我们要实现一个请假申请的审批流程,具体步骤如下:

  1. 提交申请:员工提交请假申请,填写相关信息。
  2. 部门经理审批:部门经理审核申请,决定是否批准。
  3. HR审批:如果部门经理批准,申请将提交给HR进行最终审批。
  4. 通知员工:根据审批结果,系统自动发送通知给员工。

src/main/resources目录下创建一个名为leave-request.bpmn20.xml的文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             targetNamespace="http://www.example.org/leave-request">

  <process id="leaveRequestProcess" name="Leave Request Process">
    <startEvent id="startEvent" name="Start"/>
    <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="submitForm"/>

    <userTask id="submitForm" name="Submit Leave Request" activiti:assignee="${employee}"/>
    <sequenceFlow id="flow2" sourceRef="submitForm" targetRef="managerApproval"/>

    <userTask id="managerApproval" name="Manager Approval" activiti:assignee="${manager}">
      <extensionElements>
        <camunda:formData>
          <camunda:formField id="approved" type="boolean" label="Approve?"/>
        </camunda:formData>
      </extensionElements>
    </userTask>
    <sequenceFlow id="flow3" sourceRef="managerApproval" targetRef="exclusiveGateway"/>

    <exclusiveGateway id="exclusiveGateway" name="Check Manager Approval"/>
    <sequenceFlow id="flow4" sourceRef="exclusiveGateway" targetRef="hrApproval">
      <conditionExpression xsi:type="tFormalExpression">${approved == true}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow5" sourceRef="exclusiveGateway" targetRef="notifyEmployeeRejected">
      <conditionExpression xsi:type="tFormalExpression">${approved == false}</conditionExpression>
    </sequenceFlow>

    <userTask id="hrApproval" name="HR Approval" activiti:assignee="${hr}">
      <extensionElements>
        <camunda:formData>
          <camunda:formField id="finalApproved" type="boolean" label="Final Approve?"/>
        </camunda:formField>
      </extensionElements>
    </userTask>
    <sequenceFlow id="flow6" sourceRef="hrApproval" targetRef="notifyEmployeeApproved"/>

    <serviceTask id="notifyEmployeeRejected" name="Notify Employee (Rejected)" camunda:class="com.example.NotifyEmployeeRejected"/>
    <sequenceFlow id="flow7" sourceRef="notifyEmployeeRejected" targetRef="endEvent"/>

    <serviceTask id="notifyEmployeeApproved" name="Notify Employee (Approved)" camunda:class="com.example.NotifyEmployeeApproved"/>
    <sequenceFlow id="flow8" sourceRef="notifyEmployeeApproved" targetRef="endEvent"/>

    <endEvent id="endEvent" name="End"/>
  </process>

</definitions>

这个流程包含了一个条件网关(Exclusive Gateway),用于根据部门经理的审批结果决定下一步的操作。如果申请被批准,流程将继续提交给HR进行最终审批;如果被拒绝,系统将直接通知员工。

3. 实现服务任务

在上面的流程中,我们使用了两个服务任务(NotifyEmployeeRejectedNotifyEmployeeApproved)来模拟发送通知的功能。接下来,我们需要编写这两个服务任务的实现类。在src/main/java/com/example目录下创建两个类:

  • NotifyEmployeeRejected.java

    package com.example;
    
    import org.camunda.bpm.engine.delegate.DelegateExecution;
    import org.camunda.bpm.engine.delegate.JavaDelegate;
    
    public class NotifyEmployeeRejected implements JavaDelegate {
    
    @Override
    public void execute(DelegateExecution execution) {
      String employee = (String) execution.getVariable("employee");
      System.out.println("通知员工 " + employee + ":请假申请已被拒绝。");
    }
    }
  • NotifyEmployeeApproved.java

    package com.example;
    
    import org.camunda.bpm.engine.delegate.DelegateExecution;
    import org.camunda.bpm.engine.delegate.JavaDelegate;
    
    public class NotifyEmployeeApproved implements JavaDelegate {
    
    @Override
    public void execute(DelegateExecution execution) {
      String employee = (String) execution.getVariable("employee");
      System.out.println("通知员工 " + employee + ":请假申请已批准。");
    }
    }

4. 启动并执行流程

最后,我们编写代码来启动并执行这个复杂的流程。在src/main/java/com/example目录下创建一个名为CamundaDemo.java的类,代码如下:

package com.example;

import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.ProcessEngines;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.TaskService;
import org.camunda.bpm.engine.repository.Deployment;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.task.Task;

import java.util.HashMap;
import java.util.Map;

public class CamundaDemo {

  public static void main(String[] args) {
    // 获取默认的流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    // 获取RepositoryService对象,用于管理流程定义
    RepositoryService repositoryService = processEngine.getRepositoryService();

    // 部署流程定义
    Deployment deployment = repositoryService.createDeployment()
        .addClasspathResource("leave-request.bpmn20.xml")
        .deploy();

    System.out.println("流程已部署,部署ID:" + deployment.getId());

    // 获取RuntimeService对象,用于启动流程实例
    RuntimeService runtimeService = processEngine.getRuntimeService();

    // 设置流程变量
    Map<String, Object> variables = new HashMap<>();
    variables.put("employee", "Alice");
    variables.put("manager", "Bob");
    variables.put("hr", "Charlie");

    // 启动流程实例
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leaveRequestProcess", variables);

    System.out.println("流程已启动,实例ID:" + processInstance.getId());

    // 获取TaskService对象,用于管理任务
    TaskService taskService = processEngine.getTaskService();

    // 处理第一个任务(提交申请)
    Task submitFormTask = taskService.createTaskQuery().taskAssignee("Alice").singleResult();
    System.out.println("当前任务:" + submitFormTask.getName());
    taskService.complete(submitFormTask.getId());

    // 处理第二个任务(部门经理审批)
    Task managerApprovalTask = taskService.createTaskQuery().taskAssignee("Bob").singleResult();
    System.out.println("当前任务:" + managerApprovalTask.getName());

    // 模拟部门经理批准申请
    Map<String, Object> approvalVariables = new HashMap<>();
    approvalVariables.put("approved", true);
    taskService.complete(managerApprovalTask.getId(), approvalVariables);

    // 处理第三个任务(HR审批)
    Task hrApprovalTask = taskService.createTaskQuery().taskAssignee("Charlie").singleResult();
    System.out.println("当前任务:" + hrApprovalTask.getName());

    // 模拟HR批准申请
    Map<String, Object> finalApprovalVariables = new HashMap<>();
    finalApprovalVariables.put("finalApproved", true);
    taskService.complete(hrApprovalTask.getId(), finalApprovalVariables);

    System.out.println("流程结束!");
  }
}

这段代码的主要逻辑如下:

  1. 部署BPMN文件,启动流程实例,并设置流程变量(员工、部门经理和HR的名称)。
  2. 模拟员工提交请假申请,完成第一个任务。
  3. 模拟部门经理批准申请,完成第二个任务。
  4. 模拟HR批准申请,完成第三个任务。
  5. 根据审批结果,触发相应的服务任务,发送通知给员工。

5. 运行程序

同样,在命令行中运行程序:

mvn clean compile exec:java -Dexec.mainClass="com.example.CamundaDemo"

如果一切顺利,你应该会看到如下输出:

流程已部署,部署ID:1
流程已启动,实例ID:1
当前任务:Submit Leave Request
当前任务:Manager Approval
当前任务:HR Approval
通知员工 Alice:请假申请已批准。
流程结束!

通过这个例子,我们可以看到Camunda不仅支持简单的用户任务和条件分支,还可以通过服务任务集成外部系统或执行自定义逻辑。此外,Camunda还提供了丰富的API和工具,帮助开发者更好地管理和监控复杂的业务流程。

结语:选择适合你的工作流引擎

经过前面的介绍,相信大家对Activiti和Camunda已经有了更深入的了解。无论是轻量级的Activiti,还是功能强大的Camunda,它们都在各自的领域中有着独特的优势。选择哪款引擎,取决于你的具体需求和应用场景。

  • 如果你是一个小型项目或初创公司,Activiti的简单易用和轻量级特性可能更适合你。它的API设计非常友好,能够快速集成到现有的Java应用中。
  • 如果你是一个大型企业或复杂的业务场景,Camunda的强大功能和扩展性将为你带来更多的灵活性和可靠性。它不仅支持更多的流程标准,还提供了丰富的工具和插件,帮助企业更好地管理和优化业务流程。

无论你选择哪款引擎,我们都希望这篇文章能够帮助你在工作流开发的道路上迈出坚实的第一步。未来的工作流世界充满了无限可能,让我们一起探索更多的创新和突破吧!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注