Activiti 流程

Activiti 流程

流程引擎

流程启动,运行的具体环境。

创建流程引擎

ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

创建流程引擎时,会在classpath下搜索activiti.cfg.xml配置文件,并基于此文件进行构建。

如果没有查询到配置文件,则会基于默认配置创建引擎。

可以通过编程的方式实现引擎配置。

配置内容包括: 数据库, 是否启用Job执行器,邮件服务器,历史存储,缓存配置,日志,事件监听等。

流程引擎的服务

api.services.png
// 创建引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 从引擎中获取各类服务
RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
ManagementService managementService = processEngine.getManagementService();
IdentityService identityService = processEngine.getIdentityService();
HistoryService historyService = processEngine.getHistoryService();
FormService formService = processEngine.getFormService();

所有的服务都是无状态的。

Repository Service 提供了管理和控制发布包和流程的定义操作。如:部署流程定义;查询引擎中的已有发布包和流程定义;暂停或激活发布包;获取发布包中的资源,如xml文件或是流程图片等。

Runtime Service 负责启动一个流程顶的新实例。对于每个流程定义来说,同一个时间内,可以有多个实例在执行。runtime service还可以用于获取和保存流程实例中的变量。或是用于查询流程实例,执行实例,触发实例等。

Task Service 任务相关的服务。包含功能:查询分配给用户或用户组的任务的信息;创建独立运行于流程实例外的任务;手段设置任务与用户的关联关系;认领(claim)任务, 完成(complete)任务等。

Identity Service 负责管理(创建,更新,删除,查询...)群组和用户。注意,activiti执行时不会对用户执行检查。任务可以分配给任何人,无论这个用户是否存在。

Form Service 表单服务。可选的。提供启动表单和任务表单两个概念。即在流程实例启动前展示给用户的,和完成任务时展示给用户的两种表单。注意,这是个可选服务,表单不一定需要嵌入到流程定义中。

History Service 历史数据服务。执行流程时,引擎会保存如实例启动时间,任务参与者,完成时间,执行路径等数据。Histroy Service通过查询功能获取这些数据。

Management Service 管理服务。提供查询和管理异步操作的功能。异步操作的用途包含定时器,延迟,暂停,激活等。

流程定义

流程定义的发布

编写bpmn的xml文件,并通过使用Repository Service进行发布。

示例: 某个请假流程的定义xml

<?xml version="1.0" encoding="UTF-8" ?>

<!-- targetNamespace用于指定用户自定义的类别category, 主要用于对流程实例进行分类 -->
<!-- 其等同 repositoryService.createDeployment().category("yourCatagory")...deploy(); -->
<definitions id="definitions"
             targetNamespace="http://activiti.org/bpmn20"
             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:activiti="http://activiti.org/bpmn">
  
  <!-- process 流程。 建议一个xml中只包含一个流程 -->
  <!-- id: 必要的标识属性 -->
  <process id="vacationRequest" name="Vacation request">
    
    <!-- 事件。 事件有多种类型:开始,结束,定时器,错误,边境等等--> 
    <!-- startEvent 开始事件,流程的入口 -->
    <!-- activiti:initiator 初始化变量。 这里使用流程变量employeeName记录流程发起者的名字 -->
    <startEvent id="request" activiti:initiator="employeeName">
      <!-- 扩展。这里是一个启动表单form -->
      <extensionElements>
        <activiti:formProperty id="numberOfDays" name="Number of days" type="long" value="1" required="true"/>
        <activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)" datePattern="dd-MM-yyyy hh:mm" type="date" required="true" />
        <activiti:formProperty id="vacationMotivation" name="Motivation" type="string" />
      </extensionElements>
    </startEvent>

    <!-- 顺序流,用于说明流程的流向 -->
    <sequenceFlow id="flow1" sourceRef="request" targetRef="handleRequest" />

    <!-- 任务. 包含多种类型: userTask, serviceTask, scriptTask等 -->
    <!-- 用户任务。需要由人完成的任务 -->
    <userTask id="handleRequest" name="Handle vacation request" >
      <!-- 说明文件。可以使用代码 task.getDescription() 获取 -->
      <documentation>
        <!-- ${} UEL表达式。 可以获取到流程内的变量 -->
        ${employeeName} would like to take ${numberOfDays} day(s) of vacation (Motivation: ${vacationMotivation}).
      </documentation>
      <extensionElements>
         <activiti:formProperty id="vacationApproved" name="Do you approve this vacation" type="enum" required="true">
          <activiti:value id="true" name="Approve" />
          <activiti:value id="false" name="Reject" />
        </activiti:formProperty>
        <activiti:formProperty id="managerMotivation" name="Motivation" type="string" />
      </extensionElements>
      <!-- 任务的执行人或群组。 这里是指定群组为management -->
      <potentialOwner>
        <resourceAssignmentExpression>
          <formalExpression>management</formalExpression>
        </resourceAssignmentExpression>
      </potentialOwner>
    </userTask>
    <sequenceFlow id="flow2" sourceRef="handleRequest" targetRef="requestApprovedDecision" />
    
    <!-- 网关。包含类型: 排他, 并行, 包含, 事件等 -->
    <!-- 排他网关。 是否同意请假? -->
    <exclusiveGateway id="requestApprovedDecision" name="Request approved?" />

    <!-- management同意请假后的流程 -->
    <sequenceFlow id="flow3" sourceRef="requestApprovedDecision" targetRef="sendApprovalMail">
      <conditionExpression xsi:type="tFormalExpression">${vacationApproved == 'true'}</conditionExpression>
    </sequenceFlow>
    <task id="sendApprovalMail" name="Send confirmation e-mail" />
    <sequenceFlow id="flow4" sourceRef="sendApprovalMail" targetRef="theEnd1" />
    <!-- 结束事件, 请假成功。 -->
    <endEvent id="theEnd1" />
    
    <!-- management不同意请假后的流程-->
    <sequenceFlow id="flow5" sourceRef="requestApprovedDecision" targetRef="adjustVacationRequestTask">
      <conditionExpression xsi:type="tFormalExpression">${vacationApproved == 'false'}</conditionExpression>
    </sequenceFlow>
    <userTask id="adjustVacationRequestTask" name="Adjust vacation request">
      <documentation>
        Your manager has disapproved your vacation request for ${numberOfDays} days.
        Reason: ${managerMotivation}
      </documentation>
      <extensionElements>
        <activiti:formProperty id="numberOfDays" name="Number of days" value="${numberOfDays}" type="long" required="true"/>
        <activiti:formProperty id="startDate" name="First day of holiday (dd-MM-yyy)" value="${startDate}" datePattern="dd-MM-yyyy hh:mm" type="date" required="true" />
        <activiti:formProperty id="vacationMotivation" name="Motivation" value="${vacationMotivation}" type="string" />
        <!-- 表单元素:是否再次发起请假? -->
        <activiti:formProperty id="resendRequest" name="Resend vacation request to manager?" type="enum" required="true">
          <activiti:value id="true" name="Yes" />
          <activiti:value id="false" name="No" />
        </activiti:formProperty>
      </extensionElements>
        <!-- 任务执行人为发起流程的人 -->
      <humanPerformer>
        <resourceAssignmentExpression>
          <formalExpression>${employeeName}</formalExpression>
        </resourceAssignmentExpression>
      </humanPerformer>
    </userTask>
    <sequenceFlow id="flow6" sourceRef="adjustVacationRequestTask" targetRef="resendRequestDecision" />
    
    <!-- 排他网关。 是否再次发起请假流程? -->
    <exclusiveGateway id="resendRequestDecision" name="Resend request?" />

    <!-- 再次请假, 流程回到management handleRequest处 -->
    <sequenceFlow id="flow7" sourceRef="resendRequestDecision" targetRef="handleRequest">
      <conditionExpression xsi:type="tFormalExpression">${resendRequest == 'true'}</conditionExpression>
    </sequenceFlow>

    <!-- 不再请假 -->
     <sequenceFlow id="flow8" sourceRef="resendRequestDecision" targetRef="theEnd2">
      <conditionExpression xsi:type="tFormalExpression">${resendRequest == 'false'}</conditionExpression>
    </sequenceFlow>
    <!-- 结束事件2。请假失败 -->
    <endEvent id="theEnd2" />

  </process>

</definitions>

xml对应的流程图

api.vacationRequest.png

流程的部署

    repositoryService.createDeployment()
        .name("my-process-name")
        .addClasspathResource("org/activiti/myProcess.bpmn20.xml")
        .addClasspathResource("org/activiti/myProcess.png")
        .deploy();

如果发布使用的是bpmn文件,则改为addClassResource("org/activiti/myProcess.bpmn")即可。
使用这种方式时,没有提供流程图片文件。因此可以通过配置流程引擎自动生成图片。

<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
    ...
    <property name="createDiagramOnDeploy" value="true" />
</bean>

通过API获取流程定义的图片资源

    ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
            .processDefinitionKey("yourProcessId").singleResult();
    String diagramResourceName = procDef.getDiagramResourceName();
    InputStream imageStream = 
            repositoryService.getResourceAsStream(procDef.getDeploymentId(), diagramResourceName);

流程定义的挂起与激活

// 挂起流程。流程将无法新建实例,继续执行,异步操作。
repositoryService.suspendProcessDefinitionByKey("vacationRequest");
try {
  runtimeService.startProcessInstanceByKey("vacationRequest");
} catch (ActivitiException e) {
  e.printStackTrace(); // 无法再启动该定义流程的实例
}
// 重新激活流程
repositoryService.activateProcessDefinitionByKey("vacationRequest");

流程实例

流程实例都共享一个流程定义。
在启动流程实例之前,必须保证流程定义已经被发布。然后通过流程定义的ID启动实例。(在流程模型中定义的ID,在activiti中对应于key。如流程id,任务id等)

    ProcessInstance proInst = runtimeService.startProcessInstanceByKey("yourProcessId");

创建一个流程实例,首先会进入开始事件。

在开始事件之后,它会沿着所有的外出连线执行,到达第一个任务。Activiti会把一个任务保存到数据库中。这时,分配到这个任务的用户或群组会被解析,也会保存到数据库里。

Activiti引擎会继续执行流程环节,直至遇到一个等待状态,如用户任务等。在等待状态下,当前的流程实例的状态会保存到数据库中。直至用户决定完成任务才能改变这个状态。

之后引擎会继续执行,直至遇到下一个等待状态,或是流程结束。

在引擎运行过程中,如果出现重启或是崩溃情况,流程状态也会安全的保存在数据库中。

流程的启动

流程定义发布到引擎后,就可以基于它发起新的流程实例。如先前所说,基于同一个流程定义可以同时存在多个流程实例。

// 表单元素
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employeeName", "Kermit");
variables.put("numberOfDays", new Integer(4));
variables.put("vacationMotivation", "I'm really tired!");

// 使用RuntimeService获得与流程运行相关的信息
RuntimeService runtimeService = processEngine.getRuntimeService();
// 从流程引擎中查找一个定义为"vacationRequest"的流程并启动
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("vacationRequest", variables);

// Verify that we started a new process instance
Log.info("Number of process instances: " + runtimeService.createProcessInstanceQuery().count());

流程中的任务的完成

// Fetch all tasks for the management group
TaskService taskService = processEngine.getTaskService();
// 查找当前未完成的且执行用户组是management的任务
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();
for (Task task : tasks) {
  Log.info("Task available: " + task.getName());
}

// 此处应获取到任务: adjustVacationRequestTask
Task task = tasks.get(0);

Map<String, Object> taskVariables = new HashMap<String, Object>();
taskVariables.put("vacationApproved", "false"); //拒绝了请假
taskVariables.put("managerMotivation", "We have a tight deadline!");
// 任务完成。流程进入到下一个步骤
taskService.complete(task.getId(), taskVariables);

// !!如何需要获取流程中的变量
runtimeService.getVariables(processInstanceId);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容