1. 程式人生 > >Activiti學習(3)Activiti 6.0 快速開始指南

Activiti學習(3)Activiti 6.0 快速開始指南

參考Activiti官網提供的幫助文件構建demo,原文網址:https://www.activiti.org/quick-start。其間結合其他大牛的筆記做了調整,在文中會具體說明。

在國內網上眾多快速入門的文章中,個人認為copywang在CSDN上的部落格文章【Activiti工作流引擎】官方快速入門demo比較出眾,若非自己要通過博文深化自己的理解和認識,就直接轉載了。在撰寫本文的過程中,對上文多有參考,深表感謝。

(1)$mvnProject:Maven專案的根目錄

(2)$actUnzipedPack:從http://www.activiti.org/download.html下載的解壓縮檔案的根目錄

(3)$quickStartJavaProjectName:Java專案的名稱,此處命名為ActivitiQuickStart

(4)...: 為簡化說明而省略的內容

(5)$actVer:當前執行的Activiti版本

(6)資料庫:mysql-8.0.12-winx64(原文中使用h2)

一、簡介

本文介紹使用Activiti將業務流程管理(BPM)嵌入到應用程式中的基本方法。將建立一個嵌入了BPMN標準邏輯的命令列應用程式。Activiti有先進的流程設計工具,包括基於Eclipse和基於Web的BPMN編輯器。原文中使用Activiti的Java API程式設計實現,此處會結合Eclipse中的外掛Activiti Eclipse BPMN2.0 Designer實現流程建模。

二、建立並配置Maven專案

(一)建立專案

在Eclipse中建立名為ActivitiQuickStart的Maven專案,考慮擬將程式部署到Tomcat上執行,選擇【archetype-webapp】模板原型,如圖1、圖2所示。

圖1
圖2

(二)編譯環境設定

1、完成專案建立後,會出現如下錯誤,如圖3所示:

圖3

這個問題的解決方法就是在專案的BuildPath中增加Sever Runtime庫,此處選擇安裝好的Tomcat v9.0,如圖4所示。

圖4

2、由於JRE的版本問題,專案可能會給出如下兩個警告資訊。

(1)警告資訊1:Java執行環境JRE配置不當。

Build path specifies execution environment J2SE-1.5. There are no JREs installed in the workspace that are strictly compatible with this environment.

這個問題的解決方法就是重新配置專案中的JRE環境,【點選專案右鍵】→【Build Path】→【Configure Build Path】,在Libraries中刪除原來的JRE1.5,增加已經安裝的JRE,此處安裝的是JRE10,如圖5所示。

圖5

(2)警告資訊2:編譯器配置不當。

The compiler compliance specified is 1.5 but a JRE 10 is used

在專案的【Properties】中設定【Maven】的【Project Facets】,將Jave的版本修改為10,如圖6所示。

圖6

至此,新建立的Maven專案就不再有錯誤和警告資訊,專案結構如圖7所示。

圖7

另外,在專案Properties中,設定Jave Compiler,將Compiler compliance level修改為10。

(三)配置pom.xml

在pom.xml檔案增加如下屬性和依賴:

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <jdk.version>10.0.0.2</jdk.version>
    <junit>3.8.1</junit>
    <activiti>6.0.0</activiti>
    <slf4j>1.7.21</slf4j>
    <mysql.connector>8.0.11</mysql.connector>
</properties>

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-engine</artifactId>
        <version>${activiti}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${slf4j}</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.connector}</version>
    </dependency>
</dependencies>

【右鍵單擊專案】→【Maven】→【Update Project...】,更新專案的依賴庫。

在Eclipse的Terminal中,進入專案根目錄$mvnProject,執行命令【mvn compile】,成功編譯的資訊如圖8所示。

圖8

或者直接在Tomcat伺服器上執行:【右鍵單擊專案】→【Run As】→【Run on Sever】,將專案加入到Tomcat伺服器上執行,訪問http://localhost:8080/ActivitiQuickStart/,結果如圖9所示,則表明基於Maven的web專案建立成功。

圖9

三、建立流程引擎

利用Simple Logging Facade for Java (slf4j)進行日誌記錄。將log4j.properties檔案新增到專案中。

File: $mvnProject/src/main/resources/log4j.properties

log4j.rootLogger=WARN, ACT

log4j.appender.ACT=org.apache.log4j.ConsoleAppender
log4j.appender.ACT.layout=org.apache.log4j.PatternLayout
log4j.appender.ACT.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n

新建一個包含空main函式的Java類,命名為OnboardingRequest.java。

File: $mvnProject/src/main/java/com/seu/liuds/ActivitiQuickStart/OnboardingRequest.java

package com.seu.liuds.activiti.ActivitiQuickStart;

public class OnboardingRequest {
	public static void main(String[] args) {

	}
}

使用Java程式碼的方式建立流程引擎。

package com.seu.liuds.activiti.ActivitiQuickStart;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration;

public class OnboardingRequest {
	public static void main(String[] args) {
		ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
				.setJdbcUrl("jdbc:mysql://localhost:3306/activiti?useSSL=false&serverTimezone=GMT%2B8")
				.setJdbcUsername("root")
				.setJdbcPassword("")
				.setJdbcDriver("com.mysql.cj.jdbc.Driver")
				.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
		ProcessEngine processEngine = cfg.buildProcessEngine();
		String pName = processEngine.getName();
		String ver = ProcessEngine.VERSION;
		System.out.println("ProcessEngine [" + pName + "] Version: [" + ver + "]");
	}
}

【右鍵單擊OnboardingRequest.java】→【Run As】→【Java Application】,控制檯Console中將顯示如下資訊(版本資訊會根據實際使用的版本有所不同):

ProcessEngine [default] Version: [6.0.0.4]

此外,可以在Eclipse的Terminal中對專案進行打包。首先在專案的pom.xml檔案中增加以下內容:

<build>
    <plugins>
        <!-- Maven compiler Plugin -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>${jdk.version}</source> 
                <target>${jdk.version}</target>
                <encoding>UTF-8</encoding>
                <skipTests>true</skipTests>
                <verbose>true</verbose>
                <showWarnings>true</showWarnings>
            </configuration>
        </plugin>

        <!-- Maven Assembly Plugin -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>2.4.1</version>
            <configuration>
                <!-- get all project dependencies -->
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <!-- MainClass in mainfest make a executable jar -->
                <archive>
                    <manifest>
                        <mainClass>com.seu.liuds.activiti.OnboardingRequest</mainClass>
                    </manifest>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <!-- bind to the packaging phase -->
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

在Terminal中執行【mvn package】,視窗中將顯示以下資訊:

[INFO] Building jar: D:\Workspace\EclipseWorkspace\ActivitiQuickStart\target\ActivitiQuickStart-jar-with-d
ependencies.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 36.922 s
[INFO] Finished at: 2018-08-07T00:38:50+08:00
[INFO] ------------------------------------------------------------------------

在Terminal中執行打包好的jar檔案,結果出現以下錯誤提示:

D:\Workspace\EclipseWorkspace\ActivitiQuickStart\target>java -jar ActivitiQuickStart-jar-with-dependencies.jar
錯誤: 找不到或無法載入主類 com.seu.liuds.activiti.OnboardingRequest                                                   
原因: java.lang.ClassNotFoundException: com.seu.liuds.activiti.OnboardingRequest       

在網上查詢解決方案未果,按照CSDN上劉少明flylib的博文《Java命令列執行報:找不到或無法載入主類》,採用Eclipse的【Export】功能匯出jar檔案,執行後結果如下:

D:\Workspace\test>java -jar Onboarding.jar
log4j:WARN No appenders could be found for logger (org.activiti.engine.impl.cfg.
ProcessEngineConfigurationImpl).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
ProcessEngine [default] Version: [6.0.0.4]

至此,Activiti的BPM引擎成功嵌入ActivitiQuickStart程式中。

四、部署流程例項

以一個簡單的入職流程為例,輸入資料,如果工作經驗3年以上,釋出個性化入職歡迎資訊,使用者將手動將資料輸入到後臺系統中;如果工作經驗不高於3年,資料將自動存到後臺系統。基於BPMN 2.0規範構建的Activiti流程例項如圖10所示。

圖10

流程以xml檔案形式存放,可採用視覺化的流程建模工具(如Activiti BPMN Designer)構建。此處採用官方提供的是xml配置檔案 onboarding.bpmn20.xml,存放在【/ src / main / resources /】目錄下。onboarding.bpmn20.xml加入專案後,出現瞭如圖11所示的錯誤提示。

圖11

wangpf2011的部落格中《Activiti內建動態表單的date格式問題》一文對此有較為詳細的說明,初步嘗試後沒有解決問題,暫時略過,將時間格式的屬性刪除處理。最終加入專案的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">
		<startEvent id="startOnboarding" name="Start"
			activiti:initiator="initiator"></startEvent>
		<userTask id="enterOnboardingData" name="Enter Data"
			activiti:assignee="${initiator}" activiti:candidateGroups="managers">
			<extensionElements>
				<activiti:formProperty id="fullName"
					name="Full Name" type="string"></activiti:formProperty>
				<activiti:formProperty id="yearsOfExperience"
					name="Years of Experience" type="long" required="true"></activiti:formProperty>
			</extensionElements>
		</userTask>
		<sequenceFlow
			id="sid-1337EA98-7364-4198-B5D9-30F5341D6918"
			sourceRef="startOnboarding" targetRef="enterOnboardingData"></sequenceFlow>
		<exclusiveGateway id="decision"
			name="Years of Experience" default="automatedIntroPath"></exclusiveGateway>
		<sequenceFlow
			id="sid-42BE5661-C3D5-4DE6-96F5-73D34822727A"
			sourceRef="enterOnboardingData" targetRef="decision"></sequenceFlow>
		<userTask id="personalizedIntro"
			name="Personalized Introduction and Data Entry"
			activiti:assignee="${initiator}" activiti:candidateGroups="managers">
			<extensionElements>
				<activiti:formProperty
					id="personalWelcomeTime" name="Personal Welcome Time" type="date"></activiti:formProperty>
			</extensionElements>
		</userTask>
		<endEvent id="endOnboarding" name="End"></endEvent>
		<sequenceFlow
			id="sid-37A73ACA-2E23-400B-96F3-71F77738DAFA"
			sourceRef="automatedIntro" targetRef="endOnboarding"></sequenceFlow>
		<scriptTask id="automatedIntro"
			name="Generic and Automated Data Entry" scriptFormat="javascript"
			activiti:autoStoreVariables="false">
			<script>var dateAsString = new Date().toString();
				execution.setVariable("autoWelcomeTime", dateAsString);
			</script>
		</scriptTask>
		<sequenceFlow id="automatedIntroPath"
			sourceRef="decision" targetRef="automatedIntro"></sequenceFlow>
		<sequenceFlow id="personalizedIntroPath" name="&gt;3"
			sourceRef="decision" targetRef="personalizedIntro">
			<conditionExpression xsi:type="tFormalExpression"><![CDATA[${yearsOfExperience > 3}]]></conditionExpression>
		</sequenceFlow>
		<sequenceFlow
			id="sid-BA6F061B-47B6-428B-8CE6-739244B14BD6"
			sourceRef="personalizedIntro" targetRef="endOnboarding"></sequenceFlow>
	</process>
	<bpmndi:BPMNDiagram id="BPMNDiagram_onboarding">
		<bpmndi:BPMNPlane bpmnElement="onboarding"
			id="BPMNPlane_onboarding">
			<bpmndi:BPMNShape bpmnElement="startOnboarding"
				id="BPMNShape_startOnboarding">
				<omgdc:Bounds height="35.0" width="35.0" x="155.0"
					y="142.0"></omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="enterOnboardingData"
				id="BPMNShape_enterOnboardingData">
				<omgdc:Bounds height="80.0" width="100.0" x="240.0"
					y="120.0"></omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="decision"
				id="BPMNShape_decision">
				<omgdc:Bounds height="40.0" width="40.0" x="385.0"
					y="140.0"></omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="personalizedIntro"
				id="BPMNShape_personalizedIntro">
				<omgdc:Bounds height="80.0" width="100.0" x="519.0"
					y="15.0"></omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="endOnboarding"
				id="BPMNShape_endOnboarding">
				<omgdc:Bounds height="35.0" width="35.0" x="720.0"
					y="159.0"></omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNShape bpmnElement="automatedIntro"
				id="BPMNShape_automatedIntro">
				<omgdc:Bounds height="80.0" width="100.0" x="520.0"
					y="255.0"></omgdc:Bounds>
			</bpmndi:BPMNShape>
			<bpmndi:BPMNEdge
				bpmnElement="sid-1337EA98-7364-4198-B5D9-30F5341D6918"
				id="BPMNEdge_sid-1337EA98-7364-4198-B5D9-30F5341D6918">
				<omgdi:waypoint x="190.0" y="159.0"></omgdi:waypoint>
				<omgdi:waypoint x="240.0" y="160.0"></omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge
				bpmnElement="sid-42BE5661-C3D5-4DE6-96F5-73D34822727A"
				id="BPMNEdge_sid-42BE5661-C3D5-4DE6-96F5-73D34822727A">
				<omgdi:waypoint x="340.0" y="160.0"></omgdi:waypoint>
				<omgdi:waypoint x="385.0" y="160.0"></omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge
				bpmnElement="sid-37A73ACA-2E23-400B-96F3-71F77738DAFA"
				id="BPMNEdge_sid-37A73ACA-2E23-400B-96F3-71F77738DAFA">
				<omgdi:waypoint x="570.0" y="255.0"></omgdi:waypoint>
				<omgdi:waypoint x="570.0" y="176.0"></omgdi:waypoint>
				<omgdi:waypoint x="720.0" y="176.0"></omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge bpmnElement="automatedIntroPath"
				id="BPMNEdge_automatedIntroPath">
				<omgdi:waypoint x="405.0" y="180.0"></omgdi:waypoint>
				<omgdi:waypoint x="405.0" y="295.0"></omgdi:waypoint>
				<omgdi:waypoint x="520.0" y="295.0"></omgdi:waypoint>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge bpmnElement="personalizedIntroPath"
				id="BPMNEdge_personalizedIntroPath">
				<omgdi:waypoint x="405.0" y="140.0"></omgdi:waypoint>
				<omgdi:waypoint x="405.0" y="55.0"></omgdi:waypoint>
				<omgdi:waypoint x="519.0" y="55.0"></omgdi:waypoint>
				<bpmndi:BPMNLabel>
					<omgdc:Bounds height="16.0" width="100.0" x="405.0"
						y="140.0"></omgdc:Bounds>
				</bpmndi:BPMNLabel>
			</bpmndi:BPMNEdge>
			<bpmndi:BPMNEdge
				bpmnElement="sid-BA6F061B-47B6-428B-8CE6-739244B14BD6"
				id="BPMNEdge_sid-BA6F061B-47B6-428B-8CE6-739244B14BD6">
				<omgdi:waypoint x="619.0" y="55.0"></omgdi:waypoint>
				<omgdi:waypoint x="737.0" y="55.0"></omgdi:waypoint>
				<omgdi:waypoint x="737.0" y="159.0"></omgdi:waypoint>
			</bpmndi:BPMNEdge>
		</bpmndi:BPMNPlane>
	</bpmndi:BPMNDiagram>
</definitions>

修改OnboardingRequest.java,將流程例項onboarding部署到Activiti引擎上,程式碼如下。

package com.seu.liuds.activiti.ActivitiQuickStart;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;

public class OnboardingRequest {
	public static void main(String[] args) {
		ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
				.setJdbcUrl("jdbc:mysql://localhost:3306/activiti?useSSL=false&serverTimezone=GMT%2B8")
				.setJdbcUsername("root").setJdbcPassword("").setJdbcDriver("com.mysql.cj.jdbc.Driver")
				.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
		ProcessEngine processEngine = cfg.buildProcessEngine();
		String pName = processEngine.getName();
		String ver = ProcessEngine.VERSION;
		System.out.println("ProcessEngine [" + pName + "] Version: [" + ver + "]");

		// Loads the supplied BPMN model and deploys it to Activiti Process Engine.
		RepositoryService repositoryService = processEngine.getRepositoryService();
		Deployment deployment = repositoryService.createDeployment().addClasspathResource("onboarding.bpmn20.xml")
				.deploy();
		// Retrieves the deployed model, proving that it is in the Activiti repository.
		ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
				.deploymentId(deployment.getId()).singleResult();
		System.out.println("Found process definition [" + processDefinition.getName() + "] with id ["
				+ processDefinition.getId() + "]");
	}
}

在Eclipse中以Java Application執行OnboardingRequest.java,結果如下:

ProcessEngine [default] Version: [6.0.0.4]
Found process definition [Onboarding] with id [onboarding:3:5004]

五、啟動流程例項

已經部署的流程例項,可以通過Activiti API來啟動,執行,檢視歷史記錄,並以其他方式管理流程例項。這裡使用Java code來啟動和執行例項。

修改OnboardingRequest.java,官方提供的程式碼中存在以下錯誤。

圖12

選擇將丟擲異常處理,修改後的OnboardingRequest.java程式碼如下。程式碼的註釋後續慢慢補上

package com.seu.liuds.activiti.ActivitiQuickStart;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import org.activiti.engine.FormService;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.form.FormData;
import org.activiti.engine.form.FormProperty;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import org.activiti.engine.impl.form.DateFormType;
import org.activiti.engine.impl.form.LongFormType;
import org.activiti.engine.impl.form.StringFormType;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;

public class OnboardingRequest {
	public static void main(String[] args) throws ParseException {
		ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
				.setJdbcUrl("jdbc:mysql://localhost:3306/activiti?useSSL=false&serverTimezone=GMT%2B8")
				.setJdbcUsername("root").setJdbcPassword("").setJdbcDriver("com.mysql.cj.jdbc.Driver")
				.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
		ProcessEngine processEngine = cfg.buildProcessEngine();
		String pName = processEngine.getName();
		String ver = ProcessEngine.VERSION;
		System.out.println("ProcessEngine [" + pName + "] Version: [" + ver + "]");

		// Loads the supplied BPMN model and deploys it to Activiti Process Engine.
		RepositoryService repositoryService = processEngine.getRepositoryService();
		Deployment deployment = repositoryService.createDeployment().addClasspathResource("onboarding.bpmn20.xml")
				.deploy();
		// Retrieves the deployed model, proving that it is in the Activiti repository.
		ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
				.deploymentId(deployment.getId()).singleResult();
		System.out.println("Found process definition [" + processDefinition.getName() + "] with id ["
				+ processDefinition.getId() + "]");
		
		RuntimeService runtimeService = processEngine.getRuntimeService();
	    ProcessInstance processInstance = runtimeService
	        .startProcessInstanceByKey("onboarding");
	    System.out.println("Onboarding process started with process instance id [" 
	        + processInstance.getProcessInstanceId()
	        + "] key [" + processInstance.getProcessDefinitionKey() + "]");
	    
	    TaskService taskService = processEngine.getTaskService();
	    FormService formService = processEngine.getFormService();
	    HistoryService historyService = processEngine.getHistoryService();

	    Scanner scanner = new Scanner(System.in);
	    while (processInstance != null && !processInstance.isEnded()) {
	      List<Task> tasks = taskService.createTaskQuery()
	          .taskCandidateGroup("managers").list();
	      System.out.println("Active outstanding tasks: [" + tasks.size() + "]");
	      for (int i = 0; i < tasks.size(); i++) {
	        Task task = tasks.get(i);
	        System.out.println("Processing Task [" + task.getName() + "]");
	        Map<String, Object> variables = new HashMap<String, Object>();
	        FormData formData = formService.getTaskFormData(task.getId());
	        for (FormProperty formProperty : formData.getFormProperties()) {
	          if (StringFormType.class.isInstance(formProperty.getType())) {
	            System.out.println(formProperty.getName() + "?");
	            String value = scanner.nextLine();
	            variables.put(formProperty.getId(), value);
	          } else if (LongFormType.class.isInstance(formProperty.getType())) {
	            System.out.println(formProperty.getName() + "? (Must be a whole number)");
	            Long value = Long.valueOf(scanner.nextLine());
	            variables.put(formProperty.getId(), value);
	          } else if (DateFormType.class.isInstance(formProperty.getType())) {
	            System.out.println(formProperty.getName() + "? (Must be a date m/d/yy)");
	            DateFormat dateFormat = new SimpleDateFormat("m/d/yy");
	            Date value = dateFormat.parse(scanner.nextLine());
	            variables.put(formProperty.getId(), value);
	          } else {
	            System.out.println("<form type not supported>");
	          }
	        }
	        taskService.complete(task.getId(), variables);

	        HistoricActivityInstance endActivity = null;
	        List<HistoricActivityInstance> activities = 
	            historyService.createHistoricActivityInstanceQuery()
	            .processInstanceId(processInstance.getId()).finished()
	            .orderByHistoricActivityInstanceEndTime().asc()
	            .list();
	        for (HistoricActivityInstance activity : activities) {
	          if (activity.getActivityType() == "startEvent") {
	            System.out.println("BEGIN " + processDefinition.getName() 
	                + " [" + processInstance.getProcessDefinitionKey()
	                + "] " + activity.getStartTime());
	          }
	          if (activity.getActivityType() == "endEvent") {
	            // Handle edge case where end step happens so fast that the end step
	            // and previous step(s) are sorted the same. So, cache the end step 
	            //and display it last to represent the logical sequence.
	            endActivity = activity;
	          } else {
	            System.out.println("-- " + activity.getActivityName() 
	                + " [" + activity.getActivityId() + "] "
	                + activity.getDurationInMillis() + " ms");
	          }
	        }
	        if (endActivity != null) {
	          System.out.println("-- " + endActivity.getActivityName() 
	                + " [" + endActivity.getActivityId() + "] "
	                + endActivity.getDurationInMillis() + " ms");
	          System.out.println("COMPLETE " + processDefinition.getName() + " ["
	                + processInstance.getProcessDefinitionKey() + "] " 
	                + endActivity.getEndTime());
	        }
	      }
	      // Re-query the process instance, making sure the latest state is available
	      processInstance = runtimeService.createProcessInstanceQuery()
	          .processInstanceId(processInstance.getId()).singleResult();
	    }
	    scanner.close();
	}
}

執行OnboardingRequest.java,並根據提示輸入資料,工作經驗輸入2年,執行結果如下。

Active outstanding tasks: [1]
Processing Task [Enter Data]
Full Name?
Jerry
Years of Experience? (Must be a whole number)
2
-- Start [startOnboarding] 2 ms
-- Enter Data [enterOnboardingData] 6992 ms
-- Years of Experience [decision] 5 ms
-- Generic and Automated Data Entry [automatedIntro] 683 ms
-- End [endOnboarding] 0 ms

再次執行,工作經驗輸入6年,執行結果如下。

Active outstanding tasks: [1]
Processing Task [Enter Data]
Full Name?
Tom
Years of Experience? (Must be a whole number)
6
-- Start [startOnboarding] 2 ms
-- Enter Data [enterOnboardingData] 13549 ms
-- Years of Experience [decision] 9 ms
Active outstanding tasks: [1]
Processing Task [Personalized Introduction and Data Entry]
Personal Welcome Time? (Must be a date m/d/yy)
4/8/13
-- Start [startOnboarding] 2 ms
-- Enter Data [enterOnboardingData] 13549 ms
-- Years of Experience [decision] 9 ms
-- Personalized Introduction and Data Entry [personalizedIntro] 34586 ms
-- End [endOnboarding] 0 ms

六、用Java編寫任務(未成功,待解決)

如前所述,入職流程中有一個活動“Generic and Automated Data Entry”,當工作經驗不大於3時作為“指令碼任務”執行。下面將把這個指令碼任務遷移到Java中。

建立一個新的Java類AutomatedDataDelegate。官方程式碼在Eclipse中存在如圖13所示的錯誤提示。

圖13

根據提示,刪除丟擲異常的語句,修改後的AutomatedDataDelegate.java如下。

package com.seu.liuds.activiti.ActivitiQuickStart;

import java.util.Date;

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

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

編輯onboarding.bpmn20.xml,將<scriptTask>替換為<serviceTask>,即將以下程式碼

……
<scriptTask id="automatedIntro"
    name="Generic and Automated Data Entry" scriptFormat="javascript"
    activiti:autoStoreVariables="false">
    <script>var dateAsString = new Date().toString();
        execution.setVariable("autoWelcomeTime", dateAsString);
    </script>
</scriptTask>
……

替換為

……
<serviceTask id="automatedIntro"
    name="Generic and Automated Data Entry"
    activiti:class="com.seu.liuds.activiti.AutomatedDataDelegate">
</serviceTask>
……

在Eclipse中執行OnboardingRequest.java,並未出現意想之中的結果,在工作經驗輸入2之後,出現瞭如下錯誤:

Full Name?
liuds
Years of Experience? (Must be a whole number)
2
09:16:55,115 [main] ERROR org.activiti.engine.impl.interceptor.CommandContext  - Error while closing command context
org.activiti.engine.ActivitiException: couldn't instantiate class com.seu.liuds.activiti.AutomatedDataDelegate
	at org.activiti.engine.impl.util.ReflectUtil.instantiate(ReflectUtil.java:137)
	at org.activiti.engine.impl.bpmn.helper.ClassDelegate.defaultInstantiateDelegate(ClassDelegate.java:306)
	at org.activiti.engine.impl.bpmn.helper.ClassDelegate.instantiateDelegate(ClassDelegate.java:295)
	at org.activiti.engine.impl.bpmn.helper.ClassDelegate.getActivityBehaviorInstance(ClassDelegate.java:273)
	at org.activiti.engine.impl.bpmn.helper.ClassDelegate.execute(ClassDelegate.java:217)
	at org.activiti.engine.impl.agenda.ContinueProcessOperation.executeActivityBehavior(ContinueProcessOperation.java:180)
	at org.activiti.engine.impl.agenda.ContinueProcessOperation.executeSynchronous(ContinueProcessOperation.java:131)
	at org.activiti.engine.impl.agenda.ContinueProcessOperation.continueThroughFlowNode(ContinueProcessOperation.java:89)
	at org.activiti.engine.impl.agenda.ContinueProcessOperation.run(ContinueProcessOperation.java:55)
	at org.activiti.engine.impl.interceptor.CommandInvoker.executeOperation(CommandInvoker.java:73)
	at org.activiti.engine.impl.interceptor.CommandInvoker.executeOperations(CommandInvoker.java:57)
	at org.activiti.engine.impl.interceptor.CommandInvoker.execute(CommandInvoker.java:42)
	at org.activiti.engine.impl.interceptor.TransactionContextInterceptor.execute(TransactionContextInterceptor.java:48)
	at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:63)
	at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:29)
	at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:44)
	at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:39)
	at org.activiti.engine.impl.TaskServiceImpl.complete(TaskServiceImpl.java:186)
	at com.seu.liuds.activiti.ActivitiQuickStart.OnboardingRequest.main(OnboardingRequest.java:91)
Caused by: org.activiti.engine.ActivitiClassLoadingException: Class not found: com.seu.liuds.activiti.AutomatedDataDelegate
	at org.activiti.engine.impl.util.ReflectUtil.loadClass(ReflectUtil.java:87)
	at org.activiti.engine.impl.util.ReflectUtil.instantiate(ReflectUtil.java:134)
	... 18 more
Caused by: java.lang.ClassNotFoundException: com.seu.liuds.activiti.AutomatedDataDelegate
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source)
	at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Unknown Source)
	at org.activiti.engine.impl.util.ReflectUtil.loadClass(ReflectUtil.java:288)
	at org.activiti.engine.impl.util.ReflectUtil.loadClass(ReflectUtil.java:68)
	... 19 more
Exception in thread "main" org.activiti.engine.ActivitiException: couldn't instantiate class com.seu.liuds.activiti.AutomatedDataDelegate
	at org.activiti.engine.impl.util.ReflectUtil.instantiate(ReflectUtil.java:137)
	at org.activiti.engine.impl.bpmn.helper.ClassDelegate.defaultInstantiateDelegate(ClassDelegate.java:306)
	at org.activiti.engine.impl.bpmn.helper.ClassDelegate.instantiateDelegate(ClassDelegate.java:295)
	at org.activiti.engine.impl.bpmn.helper.ClassDelegate.getActivityBehaviorInstance(ClassDelegate.java:273)
	at org.activiti.engine.impl.bpmn.helper.ClassDelegate.execute(ClassDelegate.java:217)
	at org.activiti.engine.impl.agenda.ContinueProcessOperation.executeActivityBehavior(ContinueProcessOperation.java:180)
	at org.activiti.engine.impl.agenda.ContinueProcessOperation.executeSynchronous(ContinueProcessOperation.java:131)
	at org.activiti.engine.impl.agenda.ContinueProcessOperation.continueThroughFlowNode(ContinueProcessOperation.java:89)
	at org.activiti.engine.impl.agenda.ContinueProcessOperation.run(ContinueProcessOperation.java:55)
	at org.activiti.engine.impl.interceptor.CommandInvoker.executeOperation(CommandInvoker.java:73)
	at org.activiti.engine.impl.interceptor.CommandInvoker.executeOperations(CommandInvoker.java:57)
	at org.activiti.engine.impl.interceptor.CommandInvoker.execute(CommandInvoker.java:42)
	at org.activiti.engine.impl.interceptor.TransactionContextInterceptor.execute(TransactionContextInterceptor.java:48)
	at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:63)
	at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:29)
	at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:44)
	at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:39)
	at org.activiti.engine.impl.TaskServiceImpl.complete(TaskServiceImpl.java:186)
	at com.seu.liuds.activiti.ActivitiQuickStart.OnboardingRequest.main(OnboardingRequest.java:91)
Caused by: org.activiti.engine.ActivitiClassLoadingException: Class not found: com.seu.liuds.activiti.AutomatedDataDelegate
	at org.activiti.engine.impl.util.ReflectUtil.loadClass(ReflectUtil.java:87)
	at org.activiti.engine.impl.util.ReflectUtil.instantiate(ReflectUtil.java:134)
	... 18 more
Caused by: java.lang.ClassNotFoundException: com.seu.liuds.activiti.AutomatedDataDelegate
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source)
	at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Unknown Source)
	at org.activiti.engine.impl.util.ReflectUtil.loadClass(ReflectUtil.java:288)
	at org.activiti.engine.impl.util.ReflectUtil.loadClass(ReflectUtil.java:68)
	... 19 more

嘗試了網上的各種方法,沒有解決問題的辦法,只好先擱置。猜想是新建的AutomatedDataDelegate類並沒有被編譯成.class,所以無法被識別。希望有知道的高手指點。