参考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、完成项目创建后,会出现如下错误,如图3所示:
这个问题的解决方法就是在项目的BuildPath中增加Sever Runtime库,此处选择安装好的Tomcat v9.0,如图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所示。
(2)警告信息2:编译器配置不当。
The compiler compliance specified is 1.5 but a JRE 10 is used
在项目的【Properties】中设置【Maven】的【Project Facets】,将Jave的版本修改为10,如图6所示。
至此,新创建的Maven项目就不再有错误和警告信息,项目结构如图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所示。
或者直接在Tomcat服务器上运行:【右键单击项目】→【Run As】→【Run on Sever】,将项目加入到Tomcat服务器上运行,访问http://localhost:8080/ActivitiQuickStart/,结果如图9所示,则表明基于Maven的web项目创建成功。
三、创建流程引擎
利用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>
(有关Maven Compile Plugin和Maven Assembly Plugin两个插件,参见《Maven 教程(21)— maven-compiler-plugin 插件详解》和《Maven build插件之Maven-assembly-plugin自定义打包》两篇博文)
在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]
出现的日志警告,参考《log4j:WARN Please initialize the log4j system properly解决办法》应该可以解决,此处暂时略过。
至此,Activiti的BPM引擎成功嵌入ActivitiQuickStart程序中。
四、部署流程实例
以一个简单的入职流程为例,输入数据,如果工作经验3年以上,发布个性化入职欢迎信息,用户将手动将数据输入到后台系统中;如果工作经验不高于3年,数据将自动存到后台系统。基于BPMN 2.0规范构建的Activiti流程实例如图10所示。
流程以xml文件形式存放,可采用可视化的流程建模工具(如Activiti BPMN Designer)构建。此处采用官方提供的是xml配置文件 onboarding.bpmn20.xml,存放在【/ src / main / resources /】目录下。onboarding.bpmn20.xml加入项目后,出现了如图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=">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,官方提供的代码中存在以下错误。
选择将抛出异常处理,修改后的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所示的错误提示。
根据提示,删除抛出异常的语句,修改后的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,所以无法被识别。希望有知道的高手指点。