博弈

博弈 查看完整档案

上海编辑南京工业职业技术学院  |  计算机软件 编辑上海鸿冠信息科技股份有限公司  |  技术负责人 编辑 segmentfault.com/u/lfheart 编辑
编辑

态度决定一切

个人动态

博弈 发布了文章 · 1月3日

Activiti 用户指南(网关)

网关

网关用于控制执行流程(或如BPMN 2.0所述,执行令牌),网关能够使用或生成令牌。网关以菱形图形显示,内部带有一个图标,该图标显示网关的类型。

bpmn.gateway.png

独占网关

描述

独占网关(也称为XOR网关,或更专业的称呼为基于数据的网关),用于在流程中对决策建模。当执行到达此网关时,将按照定义它们的顺序评估所有传出序列流,选择条件评估为true的序列流(或没有条件集,概念上在顺序流上定义为“ true”)以继续该流程。

请注意,传出序列流的语义与BPMN 2.0中的一般情况不同。通常,条件评估为真的所有顺序流都选择以并行方式继续进行,而使用独占网关时,仅选择一个顺序流。如果多个序列流的条件评估为true,则选择XML中定义的第一个(并且只有那个!)以继续该流程,如果无法选择顺序流,将引发异常。

图形符号

独占网关被可视化为典型的网关(即菱形形状),内部带有一个X图标,表示XOR语义。请注意,内部没有图标的网关默认为独占网关。BPMN 2.0规范不允许在同一流程定义中混合使用X和不使用X的菱形。

bpmn.exclusive.gateway.notation.png

XML表示

独占网关的XML表示很简单:一行定义网关,并在传出序列流上定义条件表达式,以以下模型为例:

bpmn.exclusive.gateway.png

用XML表示如下:

<exclusiveGateway id="exclusiveGw" name="Exclusive Gateway" />

<sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="theTask1">
  <conditionExpression xsi:type="tFormalExpression">${input == 1}</conditionExpression>
</sequenceFlow>

<sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="theTask2">
  <conditionExpression xsi:type="tFormalExpression">${input == 2}</conditionExpression>
</sequenceFlow>

<sequenceFlow id="flow4" sourceRef="exclusiveGw" targetRef="theTask3">
  <conditionExpression xsi:type="tFormalExpression">${input == 3}</conditionExpression>
</sequenceFlow>

查看原文

赞 0 收藏 0 评论 0

博弈 发布了文章 · 2019-12-26

Activiti 快速入门指南(Activiti Core)

Activiti Core

新API的目的很明确,以满足下列需求:

  • 隔离内部和外部API以提供向后兼容
  • 通过遵循单责任方法为模块化提供未来的途径
  • 减少以前版本的API的混乱情况
  • 纳入安全和身份管理
  • 对于希望依赖流行框架提供的约定的常见用例,可以减少实现的时间
  • 供基础服务的替代实现

尚未弃用旧的API,因此仍然可以自由使用它,但是强烈建议使用新的API以获得长期支持。

该API处于Beta测试阶段,这意味着可能会在GA发布之前对其进行更改和完善。

TaskRuntime API

如果要构建业务应用程序,那么为组织中的用户和组创建任务可能会很方便。

TaskRuntime API在这里可以为你提供帮助。

可以从GitHub克隆此示例:https://github.com/Activiti/activiti-examples

该部分的代码可以在“activiti-api-basic-task-example” maven模块中找到。

如果你在Spring Boot 2应用程序中运行,则只需添加activiti-spring-boot-starter依赖项和一个数据库驱动程序,即可将H2用于内存中存储。

https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-task-example/pom.xml#L45

<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>

建议使用BOM。

https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-task-example/pom.xml#L30

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.activiti.dependencies</groupId>
            <artifactId>activiti-dependencies</artifactId>
            <version>7.1.0.M5</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>
注意:可以使用以下链接检索最新版本:https://search.maven.org/search?q=activiti-dependencies

现在,切换到DemoApplication.classhttps://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-task-example/src/main/java/org/activiti/examples/DemoApplication.java#L25

然后,你将可以使用TaskRuntime

@Autowired
private TaskRuntime taskRuntime;

将bean注入应用程序后,你应该能够创建任务并与任务交互。

public interface TaskRuntime {
  TaskRuntimeConfiguration configuration();
  Task task(String taskId);
  Page tasks(Pageable pageable);
  Page tasks(Pageable pageable, GetTasksPayload payload);
  Task create(CreateTaskPayload payload);
  Task claim(ClaimTaskPayload payload);
  Task release(ReleaseTaskPayload payload);
  Task complete(CompleteTaskPayload payload);
  Task update(UpdateTaskPayload payload);
  Task delete(DeleteTaskPayload payload);
  ...
}

例如,你可以通过执行以下操作来创建任务:

https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-task-example/src/main/java/org/activiti/examples/DemoApplication.java#L45

taskRuntime.create(
            TaskPayloadBuilder.create()
                .withName("First Team Task")
                .withDescription("This is something really important")
                .withGroup("activitiTeam")
                .withPriority(10)
           .build());

只有属于activitiTeam的用户和所有者(当前登录的用户)才能看到此任务。

你可能已经注意到,可以使用TaskPayloadBuilder以流畅的方式参数化将要发送到TaskRuntime的信息。

为了处理安全性、角色和组,依赖于Spring Security模块。因为在Spring Boot应用程序内部,所以可以使用UserDetailsS​​ervice来配置可用用户及其各自的组和角色,目前在@Configuration类中执行此操作:

https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-task-example/src/main/java/org/activiti/examples/DemoApplicationConfiguration.java#L26

这里需要注意的重要一点是,为了作为用户与TaskRuntime API进行交互,你需要拥有角色:ACTIVITI_USER(授予的权限:ROLEACTIVITIUSER)。

与REST端点进行交互时,授权机制将设置当前登录的用户,但是为了这个例子,使用了一个工具类(https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-task-example/src/main/java/org/activiti/examples/SecurityUtil.java#L26),允许在上下文中设置手动选择的用户。请注意,除非你正在尝试并且想要在不经过REST端点的情况下更改用户,否则切勿这样做。查看“web”示例,以查看根本不需要该实用工具类的更多真实场景​​。

该示例中要强调的最后一件事是任务事件侦听器的注册:

https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-task-example/src/main/java/org/activiti/examples/DemoApplication.java#L89

@Bean
public TaskRuntimeEventListener taskAssignedListener() {
  return taskAssigned
           -> logger.info(
                 ">>> Task Assigned: '"
                + taskAssigned.getEntity().getName()
                +"' We can send a notification to the assignee: "
                + taskAssigned.getEntity().getAssignee());
}

你可以根据需要注册任意多个TaskRuntimeEventListener,当服务触发运行时事件时,这将使你的应用程序得到通知。

ProcessRuntime API

以类似的方式,如果要开始使用ProcessRuntime API,则需要包含与之前相同的依赖项,目标是在将来提供更大的灵活性和单独的运行时,但是目前,相同的Spring Boot Starter同时提供TaskRuntimeProcessRuntime API。

该部分的代码可以在“activiti-api-basic-process-example” maven模块内找到。

public interface ProcessRuntime {
  ProcessRuntimeConfiguration configuration();
  ProcessDefinition processDefinition(String processDefinitionId);
  Page processDefinitions(Pageable pageable);
  Page processDefinitions(Pageable pageable,
              GetProcessDefinitionsPayload payload);
  ProcessInstance start(StartProcessPayload payload);
  Page processInstances(Pageable pageable);
  Page processInstances(Pageable pageable,
              GetProcessInstancesPayload payload);
  ProcessInstance processInstance(String processInstanceId);
  ProcessInstance suspend(SuspendProcessPayload payload);
  ProcessInstance resume(ResumeProcessPayload payload);
  ProcessInstance delete(DeleteProcessPayload payload);
  void signal(SignalPayload payload);
  ...
}

TaskRuntime API相似,为了与ProcessRuntime API进行交互,当前登录的用户必须具有“ACTIVITI_USER”角色。

首先,注入ProcessRuntime

https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-process-example/src/main/java/org/activiti/examples/DemoApplication.java#L32

@Autowired
private ProcessRuntime processRuntime;

@Autowired
private SecurityUtil securityUtil;

和之前一样,我们需要SecurityUtil帮助器来定义与API交互的用户。

现在,可以开始与ProcessRuntime进行交互了:

https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-process-example/src/main/java/org/activiti/examples/DemoApplication.java#L47

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);
}

流程定义需要放在/src/main/resources/processes/中,在此示例中,定义了以下流程:

使用Spring Scheduling功能来每秒启动一个流程,从数组中拾取随机值以进行处理:

https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-process-example/src/main/java/org/activiti/examples/DemoApplication.java#L67

@Scheduled(initialDelay = 1000, fixedDelay = 1000)
public void processText() {
  securityUtil.logInAs("system");
  String content = pickRandomString();
  SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yy HH:mm:ss");
  logger.info("> Processing content: " + content
                    + " at " + formatter.format(new Date()));
  ProcessInstance processInstance = processRuntime
                  .start(ProcessPayloadBuilder
                       .start()
                       .withProcessDefinitionKey("categorizeProcess")
                       .withProcessInstanceName("Processing Content: " + content)
                       .withVariable("content", content)
                       .build());
  logger.info(">>> Created Process Instance: " + processInstance);
}

与之前一样,使用ProcessPayloadBuilder以流畅的方式参数化我们希望启动哪个流程,以及使用哪个流程变量。

现在,如果我们回顾一下流程定义,你将发现3个服务任务,为了提供这些服务任务的实现,你需要定义Connector

https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-process-example/src/main/java/org/activiti/examples/DemoApplication.java#L81

@Bean
public Connector processTextConnector() {
  return integrationContext -> {
      Map inBoundVariables = integrationContext.getInBoundVariables();
      String contentToProcess = (String) inBoundVariables.get("content")
     // Logic Here to decide if content is approved or not
     if (contentToProcess.contains("activiti")) {
        logger.info("> Approving content: " + contentToProcess);
        integrationContext.addOutBoundVariable("approved",true);
     } else {
        logger.info("> Discarding content: " + contentToProcess);
        integrationContext.addOutBoundVariable("approved",false);
     }
    return integrationContext;
  };
}

这些连接器使用Bean名称(在此示例中为“processTextConnector”)自动连接到ProcessRuntime,此bean名称是从流程定义内的serviceTask元素的implementation属性中选取的:

https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-process-example/src/main/resources/processes/categorize-content.bpmn20.xml#L22

<bpmn:serviceTask id="Task_1ylvdew" name="Process Content" implementation="processTextConnector">

这个新的Connector接口是JavaDelegate的自然演变,新版本的Activiti Core将通过将它们包装在Connector实现中来尝试重用JavaDelagate

public interface Connector {
  IntegrationContext execute(IntegrationContext integrationContext);
}

连接器接收带有流程变量的IntegrationContext,并返回修改后的IntegrationContext,其结果需要映射回流程变量。

在前面的示例中,连接器实现正在接收“content”变量,并基于内容处理逻辑添加“approved”变量。

在这些连接器内,你可能会包含系统到系统的调用,例如REST调用和基于消息的交互。

查看Maven模块activiti-api-spring-integration-example以获取更高级的示例,该示例使用Spring Integrations基于文件轮询器启动流程。

完整示例

你可以找到使用ProcessRuntimeTaskRuntime API来自动执行以下流程的示例:

该部分的代码可以在“activiti-api-basic-full-example” maven模块内找到。

作为仅ProcessRuntime的示例,它还对一些输入内容进行了分类,但是在这种情况下,该过程依赖于人工来决定是否批准内容。与之前一样,有一个调度任务,该任务每5秒创建一个新的流程实例,并且模拟用户检查是否有可用的任务要处理。

https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-full-example/src/main/java/org/activiti/examples/DemoApplication.java#L63

https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-full-example/src/main/java/org/activiti/examples/DemoApplication.java#L85

UserTask创建给一个potentialOwner,在本示例中为“activitiTeam”组,但是在这种情况下,我们不会像第一个示例那样手动创建任务,每次启动流程时,流程实例都会为我们创建任务。

https://github.com/Activiti/activiti-examples/blob/master/activiti-api-basic-full-example/src/main/resources/processes/categorize-human-content.bpmn20.xml#L38

<bpmn:userTask id="Task_1ylvdew" name="Process Content">
  <bpmn:incoming>SequenceFlow_09xowo4</bpmn:incoming>
  <bpmn:outgoing>SequenceFlow_1jzbgkj</bpmn:outgoing>
  <bpmn:potentialOwner>
    <bpmn:resourceAssignmentExpression>
      <bpmn:formalExpression>activitiTeam</bpmn:formalExpression>
    </bpmn:resourceAssignmentExpression>
  </bpmn:potentialOwner>
</bpmn:userTask>

属于该组的用户将可以领取任务并处理任务。


查看原文

赞 1 收藏 1 评论 0

博弈 发布了文章 · 2019-12-23

Activiti 用户指南(BPMN 2.0介绍)

BPMN 2.0介绍

定义流程

创建一个新的XML文件并为其命名,确保文件以.bpmn20.xml.bpmn结尾,否则引擎将不会选择该文件进行部署。

BPMN 2.0模式的根元素是definitions元素,在此元素内,可以定义多个流程定义(尽管建议在每个文件中只有一个流程定义,因为这样可以简化开发流程的后期维护)。空的流程定义如下所示,请注意,最少definitions元素仅需要xmlnstargetNamespace声明,targetNamespace可以是任何东西,对于对流程定义进行分类很有用。

<definitions
  xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
  xmlns:activiti="http://activiti.org/bpmn"
  targetNamespace="Examples">

  <process id="myProcess" name="My First Process">
    ..
  </process>

</definitions>

process元素具有两个属性:

  • id:此属性是必需的,并且映射到Activiti ProcessDefinition对象的key属性,然后,可以通过RuntimeService上的startProcessInstanceByKey方法,使用此id来启动流程定义的新流程实例,此方法将始终采用流程定义的最新部署版本。
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess");
  • 这里要注意的重要一点是,这与调用startProcessInstanceById方法不同,此方法期望Activiti引擎在部署时生成的字符串id,可以通过调用processDefinition.getId()方法进行检索。生成的id的格式为key:version,并且长度限制为64个字符。如果你收到一个ActivitiException声明生成的ID太长,请限制该流程的键字段中的文本。
  • name:此属性是可选的,并且映射到ProcessDefinitionname属性,引擎本身不使用此属性,因此,例如,它可用于在用户界面中显示更人性化的名称。

入门:10分钟的教程

在本节中,我们将介绍一个(非常简单的)业务流程,我们将使用它介绍一些基本的Activiti概念和Activiti API。

前提条件

本教程假定你正在运行Activiti演示安装程序,并且你正在使用独立的H2服务器,编辑db.properties并设置jdbc.url=jdbc:h2:tcp://localhost/activiti,然后根据H2的文档运行独立服务器。

目标

本教程的目的是学习Activiti和一些基本的BPMN 2.0概念,最终结果将是一个简单的Java SE程序,该程序将部署流程定义,并通过Activiti引擎API与该流程进行交互。还将介绍Activiti周围的一些工具,当然,在围绕业务流程构建自己的Web应用程序时,也可以使用本教程中学习的内容。

用例

用例很简单:我们有一家公司,我们称之为BPMCorp。在BPMCorp中,每月需要为公司股东编写财务报告,这是会计部门的责任,报告完成后,高层管理人员之一需要批准该文件,然后再将其发送给所有股东。

流程图

可以使用Activiti Designer以图形方式显示上述业务流程,但是,在本教程中,我们将自己键入XML,因为我们在这一点上学到的最多,我们的流程的图形化BPMN 2.0表示法如下所示:

financial.report.example.diagram.png

我们看到的是一个none Start Event(左侧的圆圈),后面是两个用户任务:“Write monthly financial report”和“Verify monthly financial report”,以none end event(右侧带有粗边框的圆圈)结尾。

XML表示

该业务流程的XML版本(FinancialReportProcess.bpmn20.xml)如下所示:

  • (none) start event告诉我们该流程的切入点是什么。
  • 用户任务声明是我们流程中人工任务的表示,请注意,第一个任务分配给会计组,而第二个任务分配给管理组。
  • 当到达none end event时,该流程结束。
  • 元素通过顺序流相互连接,这些顺序流具有源和目标,它们定义了顺序流的方向。
<definitions id="definitions"
  targetNamespace="http://activiti.org/bpmn20"
  xmlns:activiti="http://activiti.org/bpmn"
  xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">

    <process id="financialReport" name="Monthly financial report reminder process">

      <startEvent id="theStart" />

      <sequenceFlow id="flow1" sourceRef="theStart" targetRef="writeReportTask" />

      <userTask id="writeReportTask" name="Write monthly financial report" >
        <documentation>
          Write monthly financial report for publication to shareholders.
        </documentation>
        <potentialOwner>
          <resourceAssignmentExpression>
            <formalExpression>accountancy</formalExpression>
          </resourceAssignmentExpression>
        </potentialOwner>
      </userTask>

      <sequenceFlow id="flow2" sourceRef="writeReportTask" targetRef="verifyReportTask" />

      <userTask id="verifyReportTask" name="Verify monthly financial report" >
        <documentation>
          Verify monthly financial report composed by the accountancy department.
          This financial report is going to be sent to all the company shareholders.
        </documentation>
        <potentialOwner>
          <resourceAssignmentExpression>
            <formalExpression>management</formalExpression>
          </resourceAssignmentExpression>
        </potentialOwner>
      </userTask>

      <sequenceFlow id="flow3" sourceRef="verifyReportTask" targetRef="theEnd" />

      <endEvent id="theEnd" />

    </process>

</definitions>

启动流程实例

现在,我们已经创建了业务流程的流程定义,通过这样的流程定义,我们可以创建流程实例,在这种情况下,一个流程实例将与特定月份的单个财务报告的创建和验证相匹配,所有流程实例共享相同的流程定义。

为了能够根据给定的流程定义创建流程实例,我们必须首先部署该流程定义,部署流程定义意味着两件事:

  • 流程定义将存储在为你的Activiti引擎配置的持久性数据存储中,因此,通过部署业务流程,我们确保引擎重新启动后,引擎将找到流程定义。
  • BPMN 2.0流程文件将解析为内存中的对象模型,可以通过Activiti API对其进行操作。

部署可以通过多种方式进行,一种方法是通过以下API,请注意,与Activiti引擎的所有交互都是通过其服务进行的。

Deployment deployment = repositoryService.createDeployment()
  .addClasspathResource("FinancialReportProcess.bpmn20.xml")
  .deploy();

现在,我们可以使用在流程定义中定义的id(请参阅XML文件中的process元素)来启动新流程实例,请注意,Activiti术语中的此id称为key

ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("financialReport");

这将创建一个流程实例,该实例将首先经历启动事件,启动事件之后,它遵循所有输出顺序流(在这种情况下只有一个),并且到达了第一个任务(写每月财务报告),Activiti引擎现在将在持久数据库中存储任务,此时,将解决任务中附加的用户或组分配,并将其存储在数据库中,重要的是要注意,Activiti引擎将继续执行流程,直到达到等待状态(例如用户任务)为止。在这种等待状态下,流程实例的当前状态存储在数据库中,它将保持该状态,直到用户决定完成其任务为止,届时,引擎将继续运行直到达到新的等待状态或流程结束,同时,如果引擎重新启动或崩溃,则该流程的状态是安全的,并且在数据库中也处于良好状态。

创建任务后,由于用户任务活动处于等待状态,因此startProcessInstanceByKey方法将返回。在这种情况下,将任务分配给一个组,这意味着该组中的每个成员都是执行任务的候选人。

现在,我们可以将所有内容放在一起,并创建一个简单的Java程序,创建一个新的Eclipse项目,并将Activiti JAR和依赖项添加到其类路径(可以在Activiti发行版的libs文件夹中找到它们)。在调用Activiti服务之前,我们必须首先构造一个ProcessEngine,使我们能够访问这些服务。在这里,我们使用“standalone”配置,该配置构造一个ProcessEngine,该ProcessEngine使用也是演示设置中使用的数据库。

public static void main(String[] args) {

  // Create Activiti process engine
  ProcessEngine processEngine = ProcessEngineConfiguration
    .createStandaloneProcessEngineConfiguration()
    .buildProcessEngine();

  // Get Activiti services
  RepositoryService repositoryService = processEngine.getRepositoryService();
  RuntimeService runtimeService = processEngine.getRuntimeService();

  // Deploy the process definition
  repositoryService.createDeployment()
    .addClasspathResource("FinancialReportProcess.bpmn20.xml")
    .deploy();

  // Start a process instance
  runtimeService.startProcessInstanceByKey("financialReport");
}

任务列表

现在,我们可以通过添加以下逻辑通过TaskService检索此任务:

List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit").list();

请注意,我们传递给此操作的用户必须是accountancy的成员,因为它是在流程定义中声明的:

<potentialOwner>
  <resourceAssignmentExpression>
    <formalExpression>accountancy</formalExpression>
  </resourceAssignmentExpression>
</potentialOwner>

我们还可以使用任务查询API,通过组名获得相同的结果,现在,我们可以在代码中添加以下逻辑:

TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();

领取任务

会计现在需要申领任务,通过领取任务,特定用户将成为任务的受让人,并且该任务将从accountancy组其他成员的每个任务列表中消失,领取任务是通过编程完成的,如下所示:

taskService.claim(task.getId(), "fozzie");

现在,该任务已在领取该任务的个人任务列表中。

List<Task> tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();

完成任务

会计现在可以开始处理财务报告了,报告完成后,他就可以完成任务,这意味着该任务的所有工作都已完成。

taskService.complete(task.getId());

对于Activiti引擎,这是一个外部信号,表明必须继续执行流程实例,任务本身已从运行时数据中删除,接下来是任务的单个传出过渡,将执行移至第二个任务(“verification of the report”)。现在将使用与针对第一个任务所述的相同机制来分配第二个任务,所不同的只是将任务分配给管理组。

结束流程

可以按照与以前完全相同的方式检索并领取任务,完成第二个任务会将流程执行移至结束事件,从而结束流程实例,流程实例和所有相关的运行时执行数据将从数据存储中删除。

通过编程,你还可以使用historyService验证该流程是否已结束:

HistoryService historyService = processEngine.getHistoryService();
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());

代码概述

合并之前各节中的所有代码片段,你应该具有类似的内容:

public class TenMinuteTutorial {

  public static void main(String[] args) {

    // Create Activiti process engine
    ProcessEngine processEngine = ProcessEngineConfiguration
      .createStandaloneProcessEngineConfiguration()
      .buildProcessEngine();

    // Get Activiti services
    RepositoryService repositoryService = processEngine.getRepositoryService();
    RuntimeService runtimeService = processEngine.getRuntimeService();

    // Deploy the process definition
    repositoryService.createDeployment()
      .addClasspathResource("FinancialReportProcess.bpmn20.xml")
      .deploy();

    // Start a process instance
    String procId = runtimeService.startProcessInstanceByKey("financialReport").getId();

    // Get the first task
    TaskService taskService = processEngine.getTaskService();
    List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();
    for (Task task : tasks) {
      System.out.println("Following task is available for accountancy group: " + task.getName());

      // claim it
      taskService.claim(task.getId(), "fozzie");
    }

    // Verify Fozzie can now retrieve the task
    tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();
    for (Task task : tasks) {
      System.out.println("Task for fozzie: " + task.getName());

      // Complete the task
      taskService.complete(task.getId());
    }

    System.out.println("Number of tasks for fozzie: "
            + taskService.createTaskQuery().taskAssignee("fozzie").count());

    // Retrieve and claim the second task
    tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();
    for (Task task : tasks) {
      System.out.println("Following task is available for management group: " + task.getName());
      taskService.claim(task.getId(), "kermit");
    }

    // Completing the second task ends the process
    for (Task task : tasks) {
      taskService.complete(task.getId());
    }

    // verify that the process is actually finished
    HistoryService historyService = processEngine.getHistoryService();
    HistoricProcessInstance historicProcessInstance =
      historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
    System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());
  }

}

进一步增强

很容易看出,这个业务流程太简单了,无法在现实中使用。但是,当你遍历Activiti中可用的BPMN 2.0构造时,你将能够通过以下方式增强业务流程:

  • 定义充当决策的网关,这样,经理可以拒绝财务报告,将为会计重新创建财务报告的任务。
  • 声明和使用变量,以便我们可以存储或引用报告,以便可以在表单中将其可视化。
  • 在流程结束时定义服务任务,将报告发送给每个股东。

查看原文

赞 2 收藏 2 评论 0

博弈 发布了文章 · 2019-12-20

Activiti 用户指南(目录)

Activiti 用户指南

Activiti是领先的轻量级,以Java为中心的开源BPMN引擎,可满足现实世界中的流程自动化需求。

介绍

快速入门

配置

Activiti API

Spring集成

部署

BPMN 2.0构造

表单

JPA

History

Eclipse设计器

REST API

CDI集成

LDAP集成

高级

工具

查看原文

赞 0 收藏 0 评论 0

博弈 发布了文章 · 2019-12-20

Activiti 用户指南(Spring Boot集成)

Spring Boot集成

根据其网站,Spring Boot是一个应用程序框架,可以轻松创建独立的、生产级的基于Spring的应用程序,你可以“just run”,大多数Spring Boot应用程序只需要很少的Spring配置。

Spring Boot-Activiti集成目前处于试验阶段,它已经与Spring提交者一起开发,但是还处于初期。

入门

Spring Boot完全是关于配置的约定,首先,只需将activiti-spring-boot-starter依赖项添加到你的项目中,以Maven为例:

<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-spring-boot-starter</artifactId>
    <version>7.0.0.SR1</version>
</dependency>

这就是所有需要的,该依赖关系将向类路径中传递正确的Activiti和Spring依赖关系,你现在可以编写Spring Boot应用程序:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}

Activiti需要一个数据库来存储其数据,如果你运行上面的代码,它将为你提供一条提示性异常消息,你需要将数据库驱动程序依赖项添加到类路径中,现在,添加H2数据库依赖项:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>

现在可以启动该应用程序,你将看到如下输出:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.2.RELEASE)

2019-12-19 17:19:27.564  INFO 74776 --- [           main] c.i.a.ActivitiDemoApplication            : No active profile set, falling back to default profiles: default
2019-12-19 17:19:28.929  INFO 74776 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 7000 (http)
2019-12-19 17:19:28.941  INFO 74776 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-12-19 17:19:28.941  INFO 74776 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.29]
2019-12-19 17:19:29.027  INFO 74776 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-12-19 17:19:29.027  INFO 74776 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1422 ms
2019-12-19 17:19:29.070  INFO 74776 --- [           main] .s.s.UserDetailsServiceAutoConfiguration : 
2019-12-19 17:19:33.068  INFO 74776 --- [           main] o.a.engine.impl.ProcessEngineImpl        : ProcessEngine default created
2019-12-19 17:19:33.069  INFO 74776 --- [           main] o.a.e.i.a.DefaultAsyncJobExecutor        : Starting up the default async job executor [org.activiti.spring.SpringAsyncExecutor].
2019-12-19 17:19:33.069  INFO 74776 --- [      Thread-13] o.a.e.i.a.AcquireAsyncJobsDueRunnable    : {} starting to acquire async jobs due
2019-12-19 17:19:33.069  INFO 74776 --- [      Thread-14] o.a.e.i.a.AcquireTimerJobsRunnable       : {} starting to acquire async jobs due
2019-12-19 17:19:33.070  INFO 74776 --- [      Thread-15] o.a.e.i.a.ResetExpiredJobsRunnable       : {} starting to reset expired jobs
2019-12-19 17:19:34.049  INFO 74776 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@66e341e9, org.springframework.security.web.context.SecurityContextPersistenceFilter@28d739f1, org.springframework.security.web.header.HeaderWriterFilter@448462f0, org.springframework.security.web.csrf.CsrfFilter@55877274, org.springframework.security.web.authentication.logout.LogoutFilter@5ec9eefa, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@5d1bdd4a, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@5fb3111a, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@5b48f0f4, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@682e422c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7e3d2ebd, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@6f50d55c, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@546ed2a0, org.springframework.security.web.session.SessionManagementFilter@132e3594, org.springframework.security.web.access.ExceptionTranslationFilter@138f0661, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@32ae8f27]
2019-12-19 17:19:34.098  INFO 74776 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2019-12-19 17:19:34.169  INFO 74776 --- [           main] o.a.s.AbstractActivitiSmartLifeCycle     : Starting...
2019-12-19 17:19:34.193  INFO 74776 --- [           main] o.a.s.AbstractActivitiSmartLifeCycle     : Started.
2019-12-19 17:19:34.193  INFO 74776 --- [           main] o.a.s.AbstractActivitiSmartLifeCycle     : Starting...
2019-12-19 17:19:34.195  INFO 74776 --- [           main] o.a.s.AbstractActivitiSmartLifeCycle     : Started.
2019-12-19 17:19:34.248  INFO 74776 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 7000 (http) with context path ''
2019-12-19 17:19:34.251  INFO 74776 --- [           main] c.i.a.ActivitiDemoApplication            : Started ActivitiDemoApplication in 7.2 seconds (JVM running for 7.818)

因此,仅将依赖项添加到类路径并使用@EnableAutoConfiguration注解,在幕后发生了很多事情:

  • 自动创建内存中的数据源(因为H2驱动程序在类路径中),并传递给Activiti流程引擎配置。
  • 创建并公开了一个Activiti ProcessEngine bean。
  • 所有Activiti服务都作为Spring Bean公开。
  • Spring Job Executor已创建。

另外,processes文件夹中的任何BPMN 2.0流程定义都将自动部署,创建一个文件夹processes,然后向该文件夹添加一个虚拟流程定义(名为one-task-process.bpmn20.xml)。

<?xml version="1.0" encoding="UTF-8"?>
<definitions
        xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
        xmlns:activiti="http://activiti.org/bpmn"
        targetNamespace="Examples">

    <process id="oneTaskProcess" name="The One Task Process">
        <startEvent id="theStart" />
        <sequenceFlow id="flow1" sourceRef="theStart" targetRef="theTask" />
        <userTask id="theTask" name="my task" activiti:assignee="kermit" />
        <sequenceFlow id="flow2" sourceRef="theTask" targetRef="theEnd" />
        <endEvent id="theEnd" />
    </process>

</definitions>

还添加以下代码行以测试部署是否确实有效,CommandLineRunner是一种特殊的Spring bean,在应用程序启动时执行:

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

    @Bean
    public CommandLineRunner init(final RepositoryService repositoryService,
                                  final RuntimeService runtimeService,
                                  final TaskService taskService) {

        return new CommandLineRunner() {
            @Override
            public void run(String... strings) throws Exception {
                System.out.println("Number of process definitions : "
                    + repositoryService.createProcessDefinitionQuery().count());
                System.out.println("Number of tasks : " + taskService.createTaskQuery().count());
                runtimeService.startProcessInstanceByKey("oneTaskProcess");
                System.out.println("Number of tasks after process start: " + taskService.createTaskQuery().count());
            }
        };

    }

}

输出将如预期的那样:

Number of process definitions : 1
Number of tasks : 0
Number of tasks after process start : 1

更改数据库和连接池

如上所述,Spring Boot是关于配置的约定,默认情况下,通过仅在类路径上使用H2,它创建了一个内存数据源,并将其传递给Activiti流程引擎配置。

要更改数据源,只需提供Datasource bean即可覆盖默认值,我们在这里使用DataSourceBuilder类,它是Spring Boot的帮助类。如果在类路径上有Tomcat、HikariCP或Commons DBCP,则将选择其中之一(首先按Tomcat的顺序),例如,要切换到MySQL数据库:

@Bean
public DataSource database() {
    return DataSourceBuilder.create()
        .url("jdbc:mysql://127.0.0.1:3306/activiti-spring-boot?characterEncoding=UTF-8")
        .username("alfresco")
        .password("alfresco")
        .driverClassName("com.mysql.jdbc.Driver")
        .build();
}

从Maven依赖项中删除H2并将MySQL驱动程序和Tomcat连接池添加到类路径中:

现在启动应用程序后,你会看到它使用MySQL作为数据库(和Tomcat连接池框架):

org.activiti.engine.impl.db.DbSqlSession   : performing create on engine with resource org/activiti/db/create/activiti.mysql.create.engine.sql
org.activiti.engine.impl.db.DbSqlSession   : performing create on history with resource org/activiti/db/create/activiti.mysql.create.history.sql
org.activiti.engine.impl.db.DbSqlSession   : performing create on identity with resource org/activiti/db/create/activiti.mysql.create.identity.sql

多次重新启动应用程序时,你会看到任务数量增加(H2内存数据库无法在关机后生存,而MySQL可以)。

REST支持

通常,嵌入式Activiti引擎之上需要REST API(与公司中的不同服务进行交互),Spring Boot使这变得非常容易,将以下依赖项添加到类路径:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

创建一个新类,一个Spring服务,并创建两个方法:一个方法启动我们的流程,另一个方法获取给定受让人的任务列表,这里仅将简单包装Activiti调用,但是在实际情况下,这显然会更加复杂。

@Service
public class MyService {

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;

    @Transactional
    public void startProcess() {
        runtimeService.startProcessInstanceByKey("oneTaskProcess");
    }

    @Transactional
    public List<Task> getTasks(String assignee) {
        return taskService.createTaskQuery().taskAssignee(assignee).list();
    }

}

现在,我们可以通过使用@RestController注解类来创建REST端点,在这里,我们仅委托给上面定义的服务。

@RestController
public class MyRestController {

    @Autowired
    private MyService myService;

    @RequestMapping(value="/process", method= RequestMethod.POST)
    public void startProcessInstance() {
        myService.startProcess();
    }

    @RequestMapping(value="/tasks", method= RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE)
    public List<TaskRepresentation> getTasks(@RequestParam String assignee) {
        List<Task> tasks = myService.getTasks(assignee);
        List<TaskRepresentation> dtos = new ArrayList<TaskRepresentation>();
        for (Task task : tasks) {
            dtos.add(new TaskRepresentation(task.getId(), task.getName()));
        }
        return dtos;
    }

    static class TaskRepresentation {

        private String id;
        private String name;

        public TaskRepresentation(String id, String name) {
            this.id = id;
            this.name = name;
        }

         public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }

    }

}

我们添加到应用程序类中的自动组件扫描(@ComponentScan)都可以找到@Service@RestController,再次运行应用程序类,现在,我们可以使用如cURL与REST API进行交互:

curl http://localhost:8080/tasks?assignee=kermit
[]

curl -X POST  http://localhost:8080/process
curl http://localhost:8080/tasks?assignee=kermit
[{"id":"10004","name":"my task"}]

查看原文

赞 0 收藏 0 评论 0

博弈 发布了文章 · 2019-12-19

Activiti 用户指南(Spring集成)

Spring集成

可以将ProcessEngine配置为常规的Spring bean,集成的起点是类org.activiti.spring.ProcessEngineFactoryBean,该bean进行流程引擎配置并创建流程引擎,对于Spring集成,配置和引擎bean如下所示:

<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
    ...
</bean>

<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
  <property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>

注意,processEngineConfiguration bean现在使用org.activiti.spring.SpringProcessEngineConfiguration类。

事务

我们将逐步介绍在分发示例中找到的SpringTransactionIntegrationTest,以下是我们在此示例中使用的Spring配置文件(你可以在SpringTransactionIntegrationTest-context.xml中找到它),下面显示的部分包含dataSourcetransactionManagerprocessEngine和Activiti Engine服务。

当将数据源传递给SpringProcessEngineConfiguration时(使用属性“dataSource”),Activiti在内部使用org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy来包装传递的DataSource,这样做是为了确保从数据源检索的SQL连接和Spring事务能够很好地协同工作。这意味着虽然仍然允许将TransactionAwareDataSourceProxy传递到SpringProcessEngineConfiguration中,但不再需要在Spring配置中自行代理dataSource,在这种情况下,将不会发生其他包装。

确保自己在Spring配置中声明TransactionAwareDataSourceProxy时,不要将其用于已经知道Spring事务的资源(例如DataSourceTransactionManagerJPATransactionManager需要未代理的数据源)。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
                           http://www.springframework.org/schema/tx      http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

  <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
    <property name="driverClass" value="org.h2.Driver" />
    <property name="url" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
    <property name="username" value="sa" />
    <property name="password" value="" />
  </bean>

  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
  </bean>

  <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
    <property name="dataSource" ref="dataSource" />
    <property name="transactionManager" ref="transactionManager" />
    <property name="databaseSchemaUpdate" value="true" />
    <property name="asyncExecutorActivate" value="false" />
  </bean>

  <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
    <property name="processEngineConfiguration" ref="processEngineConfiguration" />
  </bean>

  <bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService" />
  <bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService" />
  <bean id="taskService" factory-bean="processEngine" factory-method="getTaskService" />
  <bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService" />
  <bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" />

...

Spring配置文件的其余部分包含我们将在此特定示例中使用的Bean和配置:

<beans>
  ...
  <tx:annotation-driven transaction-manager="transactionManager"/>

  <bean id="userBean" class="org.activiti.spring.test.UserBean">
    <property name="runtimeService" ref="runtimeService" />
  </bean>

  <bean id="printer" class="org.activiti.spring.test.Printer" />

</beans>

首先,使用任何一种Spring方法来创建应用程序上下文,在此示例中,你可以使用类路径XML资源来配置我们的Spring应用程序上下文:

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(
    "org/activiti/examples/spring/SpringTransactionIntegrationTest-context.xml");

或由于它是测试:

@ContextConfiguration("classpath:org/activiti/spring/test/transaction/SpringTransactionIntegrationTest-context.xml")

然后我们可以获取服务bean并在其上调用方法,ProcessEngineFactoryBean将向应用传播的服务添加一个额外的拦截器,Activiti服务方法上所需的事务语义,因此,例如,我们可以使用repositoryService部署这样的流程:

RepositoryService repositoryService =
  (RepositoryService) applicationContext.getBean("repositoryService");
String deploymentId = repositoryService
  .createDeployment()
  .addClasspathResource("org/activiti/spring/test/hello.bpmn20.xml")
  .deploy()
  .getId();

反之亦然,在这种情况下,Spring事务将围绕userBean.hello()方法,并且Activiti服务方法调用将加入同一事务。

UserBean userBean = (UserBean) applicationContext.getBean("userBean");
userBean.hello();

UserBean看起来像这样,记住,在上面的Spring bean配置中,我们将runtimeService注入到userBean中。

public class UserBean {

  /** injected by Spring */
  private RuntimeService runtimeService;

  @Transactional
  public void hello() {
    // here you can do transactional stuff in your domain model
    // and it will be combined in the same transaction as
    // the startProcessInstanceByKey to the Activiti RuntimeService
    runtimeService.startProcessInstanceByKey("helloProcess");
  }

  public void setRuntimeService(RuntimeService runtimeService) {
    this.runtimeService = runtimeService;
  }
}

表达式

默认情况下,使用ProcessEngineFactoryBean时,BPMN流程中的所有表达式也将看到所有Spring Bean。你可以使用可配置的映射来限制要在表达式中公开的bean,甚至根本不公开任何bean。下面的示例展示了一个单一的bean(printer),可在键“printer”下使用。要完全不暴露任何bean,只需在SpringProcessEngineConfiguration上传递一个空列表作为beans属性,如果未设置bean属性,则上下文中的所有Spring bean将可用。

<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
  ...
  <property name="beans">
    <map>
      <entry key="printer" value-ref="printer" />
    </map>
  </property>
</bean>

<bean id="printer" class="org.activiti.examples.spring.Printer" />

现在,可以在表达式中使用暴露的bean:例如,SpringTransactionIntegrationTesthello.bpmn20.xml显示了如何使用UEL方法表达式调用Spring bean上的方法:

<definitions id="definitions">

  <process id="helloProcess">

    <startEvent id="start" />
    <sequenceFlow id="flow1" sourceRef="start" targetRef="print" />

    <serviceTask id="print" activiti:expression="#{printer.printMessage()}" />
    <sequenceFlow id="flow2" sourceRef="print" targetRef="end" />

    <endEvent id="end" />

  </process>

</definitions>

Printer如下所示:

public class Printer {

  public void printMessage() {
    System.out.println("hello world");
  }
}

Spring bean配置(也如上所示)如下所示:

<beans>
  ...

  <bean id="printer" class="org.activiti.examples.spring.Printer" />

</beans>

自动资源部署

Spring集成还具有用于部署资源的特殊功能,在流程引擎配置中,你可以指定一组资源,创建流程引擎后,将扫描并部署所有这些资源。有适当的过滤器可以防止重复部署,仅当资源实际更改时,新部署才会部署到Activiti数据库,这在许多用例中是有道理的,在该用例中,Spring容器经常重新启动(例如,测试)。

这是一个例子:

<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
  ...
  <property name="deploymentResources"
    value="classpath*:/org/activiti/spring/test/autodeployment/autodeploy.*.bpmn20.xml" />
</bean>

<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
  <property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>

默认情况下,以上配置会将与过滤条件匹配的所有资源分组到Activiti引擎的单个部署中,重复过滤以防止重新部署未使用的资源适用于整个部署。在某些情况下,这可能不是你想要的,例如,如果你以这种方式部署一组流程资源,而这些资源中只有一个流程定义已更改,整个部署将被认为是新的,并且该部署中的所有流程定义将被重新部署,从而导致每个流程定义的新版本,即使实际上只更改了一个。

为了能够自定义确定部署的方式,可以在SpringProcessEngineConfiguration中指定一个附加属性deploymentMode。此属性定义了从与过滤器匹配的资源集中确定部署的方式,此属性默认支持3个值:

  • default:将所有资源分组到一个部署中,并将重复过滤应用于该部署,这是默认值,如果你未指定值,则将使用它。
  • single-resource:为每个单独的资源创建一个单独的部署,并将重复的过滤应用于该部署,这是你用于分别部署每个流程定义,并且仅在更改后才创建新流程定义版本的值。
  • resource-parent-folder:为共享相同父文件夹的资源创建单独的部署,并对该部署应用重复过滤,该值可用于为大多数资源创建单独的部署,但仍可以通过将它们放置在共享文件夹中进行分组,这是一个有关如何为deploymentMode指定single-resource配置的示例:
<bean id="processEngineConfiguration"
    class="org.activiti.spring.SpringProcessEngineConfiguration">
  ...
  <property name="deploymentResources" value="classpath*:/activiti/*.bpmn" />
  <property name="deploymentMode" value="single-resource" />
</bean>

除了将上面列出的值用于deploymentMode之外,你可能还需要自定义行为来确定部署,如果是这样,则可以创建SpringProcessEngineConfiguration的子类并重写getAutoDeploymentStrategy(String deploymentMode)方法,此方法确定将哪种部署策略用于deploymentMode配置的特定值。

单元测试

与Spring集成时,可以使用标准的Activiti测试工具轻松地测试业务流程,以下示例显示如何在典型的基于Spring的单元测试中测试业务流程:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:org/activiti/spring/test/junit4/springTypicalUsageTest-context.xml")
public class MyBusinessProcessTest {

  @Autowired
  private RuntimeService runtimeService;

  @Autowired
  private TaskService taskService;

  @Autowired
  @Rule
  public ActivitiRule activitiSpringRule;

  @Test
  @Deployment
  public void simpleProcessTest() {
    runtimeService.startProcessInstanceByKey("simpleProcess");
    Task task = taskService.createTaskQuery().singleResult();
    assertEquals("My Task", task.getName());

    taskService.complete(task.getId());
    assertEquals(0, runtimeService.createProcessInstanceQuery().count());

  }
}

注意,要使此方法起作用,你需要在Spring配置中定义一个org.activiti.engine.test.ActivitiRule bean(在上面的示例中通过自动装配注入)。

<bean id="activitiRule" class="org.activiti.engine.test.ActivitiRule">
  <property name="processEngine" ref="processEngine" />
</bean>

JPA与Hibernate 4.2.x

在Activiti Engine中的服务任务或侦听器逻辑中使用Hibernate 4.2.x JPA时,需要一个附加的Spring ORM依赖项,对于Hibernate 4.1.x或更低版本不需要,应添加以下依赖项:

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-orm</artifactId>
  <version>${org.springframework.version}</version>
</dependency>

查看原文

赞 0 收藏 0 评论 0

博弈 发布了文章 · 2019-12-19

Activiti 快速入门指南(用Java编写服务任务)

用Java编写服务任务

如前所述,我们的流程具有“Generic and Automated Data Entry”活动(虚拟后端调用),当入职经验不超过3年时,将有条件地执行该活动,如所提供的,这是一个“脚本任务”。在这种情况下,执行一小段Javascript来说明系统处理步骤,我们将把这个脚本任务迁移到Java中,以说明Java的强大功能如何满足各种流程要求。

创建一个新的Java类,如下所示:

文件:$mvnProject/src/main/java/com/example/AutomatedDataDelegate.java

package com.example;

import java.util.Date;

import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;

public class AutomatedDataDelegate implements JavaDelegate {

  @Override
  public void execute(DelegateExecution execution) throws Exception {
    Date now = new Date();
    execution.setVariable("autoWelcomeTime", now);
    System.out.println("Faux call to backend for [" 
    + execution.getVariable("fullName") + "]");
  }

}

将脚本任务更改为指向AutomatedDataDelegate的服务任务。

文件:$mvnProject/src/main/resources/onboarding.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" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/processdef">
  <process id="onboarding" name="Onboarding" isExecutable="true">
...
    <scriptTask id="automatedIntro" name="Generic and Automated Data Entry" scriptFormat="javascript" activiti:autoStoreVariables="false">
      <script><![CDATA[var dateAsString = new Date().toString();
execution.setVariable("autoWelcomeTime", dateAsString);]]></script>
    </scriptTask>
...

替换为:

<?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" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/processdef">
  <process id="onboarding" name="Onboarding" isExecutable="true">
...
   <serviceTask id="automatedIntro" name="Generic and Automated Data Entry" activiti:class="com.example.AutomatedDataDelegate"></serviceTask>

通过运行“mvn package”来打包代码。

像以前一样运行Java程序,注意以下示例输出。

ProcessEngine [default] Version: [5.22.0.0]
Found process definition [Onboarding] with id [onboarding:1:4]
Onboarding process started with process instance id [5] key [onboarding]
Active outstanding tasks: [1]
Processing Task [Enter Data]
Full Name?
John Doe
Years of Experience? (Must be a whole number)
3
Faux call to backend for [John Doe]
BEGIN Onboarding [onboarding] Sun Nov 27 22:57:32 EST 2016
-- Start [startOnboarding] 4 ms
-- Enter Data [enterOnboardingData] 10153 ms
-- Years of Experience [decision] 2 ms
-- Generic and Automated Data Entry [automatedIntro] 0 ms
-- End [endOnboarding] 0 ms
COMPLETE Onboarding [onboarding] Sun Nov 27 22:57:42 EST 2016
观察输出“Faux call to backend for [John Doe]”,该图说明了如何访问先前设置的流程变量。

这个简单的示例说明了在应用程序中添加流程驱动的抽象的简便性和强大功能。


上一篇:运行流程实例

查看原文

赞 0 收藏 0 评论 0

博弈 发布了文章 · 2019-12-18

Activiti 用户指南(表达式)

表达式

Activiti使用UEL进行表达式解析,UEL代表统一表达语言,并且是EE6规范的一部分(有关详细信息,请参阅EE6规范),为了在所有环境中支持最新UEL规范的所有功能,Activiti使用了JUEL的修改版。

表达式可用于例如Java服务任务、执行侦听器、任务侦听器和条件序列流。尽管有两种类型的表达式,值表达式和方法表达式,但Activiti对此进行了抽象,因此它们都可以在需要表达式的地方使用。

  • 值表达式:解析为一个值,默认情况下,所有流程变量都可以使用,此外,所有spring-beans(如果使用Spring)都可以在表达式中使用,一些例子:

    ${myVar}
    ${myBean.myProperty}
  • 方法表达式:调用带有或不带有参数的方法,调用不带参数的方法时,请确保在方法名称后添加空括号(因为这会将表达式与值表达式区分开),传递的参数可以是文字值或自行解析的表达式,例子:

    ${printer.print()}
    ${myBean.addNewOrder('orderName')}
    ${myBean.doSomething(myVar, execution)}

请注意,这些表达式支持解析原语(包括比较它们)、bean、列表、数组和映射。

在所有流程变量之上,有一些可用于表达式的默认对象:

  • execution:包含有关正在进行的执行的其他信息的DelegateExecution
  • task:包含有关当前Task的其他信息的DelegateTask,注意:仅在从任务侦听器求值的表达式中有效。
  • authenticatedUserId:当前已认证的用户的ID,如果没有用户通过身份验证,则该变量不可用。

查看原文

赞 0 收藏 0 评论 0

博弈 发布了文章 · 2019-12-18

Activiti 用户指南(瞬态变量)

瞬态变量

瞬态变量是行为与常规变量类似但不持久的变量,通常,瞬时变量用于高级用例。

以下内容适用于瞬态变量:

  • 瞬态变量根本没有存储任何历史记录。
  • 与常规变量一样,瞬态变量在设置时放在最高父级上,这意味着在执行中设置变量时,瞬态变量实际上存储在流程实例执行中,与常规变量一样,如果应在特定执行或任务上设置变量,则该方法存在局部变量。
  • 只能在流程定义中的下一个等待状态之前访问瞬态变量,之后,它们将消失,等待状态是指流程实例中将其持久存储到数据存储中的点,请注意,在此定义中,异步活动也是等待状态!
  • 只能通过setTransientVariable(name, value)设置瞬态变量,但是在调用getVariable(name)时也会返回瞬态变量(也存在getTransientVariable(name),它仅检查瞬态变量),这样做的原因是使表达式的编写变得容易,并且使用变量的现有逻辑对这两种类型均适用。
  • 瞬态变量将具有相同名称的持久变量隐藏起来,这意味着当在流程实例上同时设置持久变量和瞬态变量并且使用了getVariable("someVariable")时,将返回瞬态变量值。

瞬态变量可以在暴露常规变量的大多数地方获得和/或设置:

  • JavaDelegate实现中的DelegateExecution
  • ExecutionListener实现中的DelegateExecution和在TaskListener实现中的DelegateTask
  • 通过执行对象在脚本任务中。
  • 通过运行时服务启动流程实例时。
  • 完成任务时。
  • 调用runtimeService.trigger方法时。

这些方法遵循常规流程变量的命名约定:

void setTransientVariable(String variableName, Object variableValue);
void setTransientVariableLocal(String variableName, Object variableValue);
void setTransientVariables(Map<String, Object> transientVariables);
void setTransientVariablesLocal(Map<String, Object> transientVariables);

Object getTransientVariable(String variableName);
Object getTransientVariableLocal(String variableName);

Map<String, Object> getTransientVariables();
Map<String, Object> getTransientVariablesLocal();

void removeTransientVariable(String variableName);
void removeTransientVariableLocal(String variableName);

下面的BPMN图显示了一个典型示例:

api.transient.variable.example.png

假设Fetch Data服务任务调用一些远程服务(例如,使用REST),我们还假设启动流程实例时需要并需要提供一些配置参数,同样,这些配置参数对于历史审核目的也不重要,因此我们将它们作为瞬态变量传递:

ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder()
       .processDefinitionKey("someKey")
       .transientVariable("configParam01", "A")
       .transientVariable("configParam02", "B")
       .transientVariable("configParam03", "C")
       .start();

请注意,在达到用户任务并将其持久化到数据库之前,变量将一直可用,例如,在Additional Work用户任务中,它们不再可用,另请注意,如果提取数据本来是异步的,那么在该步骤之后它们也将不可用。

Fetch Data(简化)可能类似于:

public static class FetchDataServiceTask implements JavaDelegate {
  public void execute(DelegateExecution execution) {
    String configParam01 = (String) execution.getVariable(configParam01);
    // ...

    RestReponse restResponse = executeRestCall();
    execution.setTransientVariable("response", restResponse.getBody());
    execution.setTransientVariable("status", restResponse.getStatus());
  }
}

Process Data将获取响应瞬态变量,对其进行解析并将相关数据存储在实际的流程变量中,以备稍后使用。

离开独占网关的序列流条件不考虑使用持久变量还是瞬态变量(在这种情况下是状态瞬态变量):

<conditionExpression xsi:type="tFormalExpression">${status == 200}</conditionExpression>

查看原文

赞 0 收藏 0 评论 0

博弈 发布了文章 · 2019-12-18

Activiti 用户指南(变量)

变量

每个流程实例都需要并使用数据来执行其存在的步骤,在Activiti中,此数据称为变量,这些变量存储在数据库中,变量可用于表达式中(例如,在独占网关中选择正确的传出序列流)、在java服务任务中调用外部服务时(例如,提供输入或存储服务调用的结果)等。

流程实例可以具有变量(称为流程变量),也可以具有执行(它们是指向流程活动位置的特定指针),并且用户任务可以具有变量。流程实例可以具有任意数量的变量,每个变量都存储在ACT_RU_VARIABLE数据库表的一行中。

任何startProcessInstanceXXX方法都具有可选参数,以在创建和启动流程实例时提供变量,例如,从RuntimeService中:

ProcessInstance startProcessInstanceByKey(String processDefinitionKey, Map<String, Object> variables);

可以在流程执行期间添加变量,例如(RuntimeService):

void setVariable(String executionId, String variableName, Object value);
void setVariableLocal(String executionId, String variableName, Object value);
void setVariables(String executionId, Map<String, ? extends Object> variables);
void setVariablesLocal(String executionId, Map<String, ? extends Object> variables);

请注意,可以为给定执行将变量设置为本地(请记住,流程实例由执行树组成),该变量仅在该执行中可见,而在执行树中不可见。如果数据不应该传播到流程实例级别,或者该变量在流程实例中的某个路径具有新值(例如,使用并行路径时),则这很有用。

也可以再次获取变量,如下所示,请注意,TaskService上存在类似的方法,这意味着任务,像执行一样,可以具有仅在任务期间有效的局部变量。

Map<String, Object> getVariables(String executionId);
Map<String, Object> getVariablesLocal(String executionId);
Map<String, Object> getVariables(String executionId, Collection<String> variableNames);
Map<String, Object> getVariablesLocal(String executionId, Collection<String> variableNames);
Object getVariable(String executionId, String variableName);
<T> T getVariable(String executionId, String variableName, Class<T> variableClass);

变量通常用于Java委托、表达式、执行侦听器或任务侦听器、脚本等中,在这些构造中,当前执行或任务对象是可用的,并且可用于变量设置和/或检索,最简单的方法是:

execution.getVariables();
execution.getVariables(Collection<String> variableNames);
execution.getVariable(String variableName);

execution.setVariables(Map<String, object> variables);
execution.setVariable(String variableName, Object value);

请注意,上述所有内容都可以使用带有local的变体。

由于历史原因(以及向后兼容的原因),在进行上述任何调用时,实际上都会从数据库中获取所有变量,这意味着,如果你有10个变量,并且仅通过getVariable("myVariable")获得一个变量,则将在后台获取和缓存另外9个变量,这还不错,因为后续调用不会再次命中数据库。例如,当你的流程定义具有三个连续的服务任务(因此有一个数据库事务)时,在第一个服务任务中使用一个调用来提取所有变量可能比分别获取每个服务任务中所需的变量更好,请注意,这适用于获取和设置变量。

当然,当使用大量变量或仅当你想严格控制数据库查询和流量时,这是不合适的,自Activiti 5.17起,通过添加具有可选参数的新方法,引入了新方法以对此进行更严格的控制,该可选参数告诉引擎是否需要在后台获取和缓存所有变量:

Map<String, Object> getVariables(Collection<String> variableNames, boolean fetchAllVariables);
Object getVariable(String variableName, boolean fetchAllVariables);
void setVariable(String variableName, Object value, boolean fetchAllVariables);

对参数fetchAllVariables使用true时,行为将完全如上所述:在获取或设置变量时,将提取并缓存所有其他变量。

但是,当使用false作为值时,将使用特定查询,并且不会获取或缓存其他任何变量,这里只缓存该问题的变量的值,以供后续使用。


查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 722 次点赞
  • 获得 7 枚徽章 获得 0 枚金徽章, 获得 2 枚银徽章, 获得 5 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • Elasticsearch 参考指南

    Elasticsearch是一个高度可扩展的开源全文搜索和分析引擎,它允许你快速,近实时地存储,搜索和分析大量数据,它通常用作底层引擎/技术,为具有复杂搜索功能和要求的应用程序提供支持。

  • Spring Boot 参考指南

    Spring Boot使你可以轻松地创建可运行的独立的、生产级的基于Spring的应用程序,我们对Spring平台和第三方库有自己的看法,这样你就可以以最低限度工作开始了,大多数Spring Boot应用程序都需要很少的Spring配置。

  • Kibana 用户指南

    Kibana是一个开源的分析和可视化平台,旨在与Elasticsearch协同工作,你使用Kibana搜索、查看和与存储在Elasticsearch索引中的数据进行交互,你可以轻松地执行高级数据分析,并在各种图表,表格和地图中可视化你的数据。

  • kafka 文档

    Apache Kafka®是一个分布式流平台:发布和订阅记录流,类似于消息队列或企业消息传递系统、以容错持久的方式存储记录流、在记录流发生的时候处理它们。

  • Java™ 教程

    Java教程是希望使用Java编程语言创建应用程序的程序员的实用指南,其中包括数百个完整的工作示例和数十个课程,相关课程组被组织成“教程”。

  • Hyperledger Fabric

    Hyperledger Fabric是一个开源的企业级许可分布式分类账技术(DLT)平台,专为在企业环境中使用而设计,与其他流行的分布式分类帐或区块链平台相比,可提供一些关键的差异化功能。

  • Docker 文档

    Docker提供了一种将应用程序安全地隔离在容器中运行的方法,并将其与所有依赖项和库打包在一起。

  • Drools 文档

    Drools是一个基于正向链接和反向链接推理的规则引擎,允许快速可靠地评估业务规则和复杂的事件处理的业务规则管理系统。规则引擎也是创建expert系统的基础构件,在人工智能中,是一个模拟人类专家决策能力的计算机系统。

  • Logstash 参考指南

    Logstash是一个具有实时管道功能的开源数据收集引擎,Logstash可以动态地将来自不同数据源的数据统一起来,并将数据规范化为你选择的目的地,清理和大众化你的所有数据,用于各种高级下游分析和可视化用例。

  • Metricbeat 参考指南

    Metricbeat是一个轻量级的托运工,你可以在服务器上安装它,定期从操作系统和服务器上运行的服务收集指标,Metricbeat取得它收集的指标和统计数据,并将它们发送到你指定的输出,例如Elasticsearch或Logstash。

  • Spring Framework 参考文档

    Spring Framework为任何部署平台上基于java的现代企业应用程序提供了一个全面的编程和配置模型。Spring的一个关键元素是应用程序级别的基础设施支持:Spring关注企业应用程序的“管道”,这样团队就可以专注于应用程序级别的业务逻辑,而不必与特定的部署环境有不必要的联系。

  • Spring Cloud 参考文档

    Spring Cloud侧重于为典型用例提供良好的开箱即用体验,并提供可扩展性机制来覆盖其他。包含一下特性:分布式/版本化配置、服务注册及发现、路由、服务调用、负载均衡、熔断器、分布式消息传递。

  • Elasticsearch Java REST Client

    用于Elasticsearch的官方REST Client

  • Node.js 文档

    Node.js®是基于Chrome的V8 JavaScript引擎构建的JavaScript运行时。

  • ZooKeeper 文档

    ZooKeeper是分布式应用程序的高性能协调服务,它公开了公共服务 - 例如命名、配置管理、同步和组服务 - 在一个简单的接口中,这样你就不用从头开始写了,你可以使用它来实现一致性、组管理、领导者选举和存在协议,你可以根据自己的需要在它之上构建。

  • Grafana 文档

    漂亮的分析与监控开源平台,适用于所有指标的分析平台,Grafana允许你查询、可视化、提醒和理解你的指标,无论它们存储在何处,与你的团队一起创建、探索和共享仪表盘,并培养数据驱动的文化。

  • Golang 文档

    Go是一种开源编程语言,可以轻松构建简单,可靠,高效的软件。

  • Jenkins 用户文档

    Jenkins是一个独立的开源自动化服务器,可用于自动执行与构建、测试、交付或部署软件相关的各种任务,作为领先的开源自动化服务器,Jenkins提供数百个插件来支持构建,部署和自动化任何项目。

  • MySQL™ 参考手册

    这是MySQL™参考手册,它分别记录了MySQL 8.0到8.0.15,以及基于NDB 8.0版到8.0.15-ndb-8.0.15的NDB Cluster版本。

  • EOSIO 指南

    EOSIO是一个免费的开源区块链软件协议,为开发人员和企业家提供构建、部署和运行高性能去中心化应用程序(DAPP)的平台。

  • Express 文档

    基于Node.js的快速、开发、极简主义的Web框架,Express是一个最小且灵活的Node.js Web应用程序框架,为Web和移动应用程序提供了一组强大的功能。

  • eosjs 文档

    用于使用EOSIO RPC API与基于EOSIO的区块链集成的Javascript API。

  • Spring 指南

    无论你正在构建什么,这些指南都旨在让你尽快提高工作效率。

  • Java虚拟机规范

    Java虚拟机是Java平台的基石,它是该技术的组成部分,负责其硬件和操作系统的独立性,其编译的代码很小以及保护用户免受恶意程序攻击的能力。

  • Redis 参考文档

    Redis是开放源代码(BSD许可)的内存中数据结构存储,用作数据库、缓存和消息代理。它支持例如字符串、哈希、列表、集、带范围查询的排序集、位图、超日志,带有半径查询和流的地理空间索引等数据结构。Redis具有内置的复制、Lua脚本、LRU清除、事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供高可用性。

  • Activiti 用户指南

    Activiti是领先的轻量级,以Java为中心的开源BPMN引擎,可满足现实世界中的流程自动化需求。

注册于 2017-01-02
个人主页被 6.4k 人浏览