一、流程引擎的API和服务(services)
引擎的API是影响Activiti最常见的一种方法。我们一开始最关注的中心是ProcessEngine,像之前描述的那样,流程引擎可以被多种方式创建。从这个流程引擎里面,你能获得各个包含workflow/BPM方法的服务。流程引擎和这些获得的服务是线程安全的。所以你能为整个服务器保留这些中的一个引用。
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();
ProcessEngines.getDefaultProcessEngine();这个方法被调用之后,会首先初始化并且创建一个流程引擎,并且以后会一直返回同一个流程引擎。ProcessEngine.init()和ProcessEngine.destroy方法会被用来创建和关闭所有流程引擎的属性。
ProcessEngine这个类会被所有的activiti.cfg.xml和activiti-context.xml文件扫描到。对于所有的activiti.cfg.xml来说,流程引擎都会用一种典型的方式被创建。这种方式就是:
ProcessEngineConfiguration.
createProcessEngineConfigurationFromInputStream(inputStream).
buildProcessEngine()。
对于所有的activiti-context.xml来说,流程引擎会被用Spring的方式来创建。这种方式是:
首先Spring的应用上下文被创建,然后流程定义会在应用的上下文中获得。
所有的Services服务是没有国界的。这就意味着你可以很轻松的在一个集群的多个节点中运行Activiti,每一个都用到相同的数据库,并且在以前的调用中,哪一个机器实际上被执行不会出现错误。任何服务的调用都是同等的,不管它在哪里被执行。
当用流程引擎工作的时候,第一个被需要的服务大概就是RepositoryService。这个服务为管理、多个部署和流程定义都提供了操作。这里不作详细介绍,一个流程定义是BPMN 2.0的一个java相关。它是一个流程每一步的结构和行为的表现。一个部署包是Activiti引擎下的包的单元。一个部署包能包含多个BPMN 2.0 xml文件和其他的一些资源。一个部署包里面包含什么由开发者决定。这些包括从单一的流程BPMN 2.0文件到一个整个的流程包和相关的资源。RepositoryService允许去部署这样的包。部署一个部署包意味着这是上传到引擎中,引擎中的所有的流程在被存到数据库之前都会被检查和解析。从这点来看,这个部署包被系统能识别,并且任何流程都被包含进这个部署包中,这样的部署包现在才能被启动。
进一步说,这样的服务允许去:
- 为引擎在部署包和流程定义中提供查询
- 终止或者激活部署包作为整个或者特别的流程定义。终止意味着不能再在部署包和流程定义中进行操作,当然,激活意味着相反的操作。
- 取回各种各样的资源,比如包含部署包或者流程程序的文件。然后这些资源被引擎自动发布。
- 取回一个流程定义的pojo版本,这个版本能被用于用java比xml反省流程。
Tasks是BPM的核心,比如Activiti。它需要被系统中的真实用户所执行。在TaskService中,围绕着任务的每一件事情都是分组的。比如:
- 查询分配给人或组的任务
- 创建新的单独任务。这些任务都不跟流程实例有关。
- 操纵哪一个用户的任务被分配或者在一些方式下哪一些用户卷入到任务中。
- 请求或者完成一个任务。请求意味着有些人被分配到到任务,意味着这个人将会完成这个任务。完成意味着正在做任务的某一部分工作。
IdentityService是相当简单的。它允许对于用户和组的管理。在运行的时候,Activiti实际上不对用户作任何检查。这对于理解Activiti是很重要的。举个例子,一个任务能分配给任何人,但是如果系统知道这个用户,引擎不会去区分它。这是因为Activiti引擎也能和服务一起结合使用。
FormService是一个可选的服务。意味着Activiti没有FormService,也能正确的被使用,不会牺牲消失任何其他的功能。这个服务介绍了start form和task form的概念。一个start form是一个表单,这个表单在流程实例启动之前展现给用户。然而一个task form是一个用户想要去完成一个表单的时候,展示的表单。Activiti允许在BPMN 2.0的流程定义中去定义这些表单。
HistoryService暴露了所有被Activiti 引擎生成的历史数据。当流程执行的时候,一些数据能被引擎保留。比如流程实例开始的时间,谁做的哪个任务,这个任务完成花了多长时间,在这个流程实例中,哪个路径是被跟踪的等等。这个服务暴露了主要的访问数据的查询方法。
用activiti的时候,当编码自定义应用的时候,ManagementService是不需要的。它允许去取回数据库表信息和表的元数据。进一步说,它暴露了对计划的查询和管理操作。在Activiti里,计划被用于各种各样的事情,比如计时器,异步连续,延迟暂停/激活等。这些话题会在更多的细节中进行讨论。
要获得更多的关于服务和引擎API的详细信息,请参考doc文档。
二、异常策略
在activiti中,最基本的异常是org.activiti.engine.ActivitiException,一个未经检查的异常。这个异常在任何时间都能被API抛出,但是除非这个异常是发生在那些在doc文档中记录的那些特殊的方法。举个例子,从TaskService中抛出的一个异常:
/** * 当任务被成功执行的时候调用. * @param taskId 是要去完成的任务的ID,不能为空. * @throws ActivitiObjectNotFoundException 当给出的id没有任务存在的时候,会抛出异常. */ void complete(String taskId);
在上面的例子中,当传的id任务不存在时,会抛出一个异常。同样,因此在javadoc中明确的规定任务id不能为空,当传过来的是个null,就会抛出ActivitiIllegalArgumentException。
即使我们想避开一个大级别的异常,当在特殊情况下一些异常被抛出的时候,下面的子类会被添加。在经过process-execution或者API-invokation报的错如果不在下面列出的异常之内,他们通常都会抛出一个ActivitiException。
- ActivitiWrongDbException:当Activiti引擎发现在数据库概要和引擎版本不匹配的时候会抛此异常。
- ActivitiOptimisticLockingException:在数据保存的时候,当同时被访问相同数据进入的时候,引起的乐观所。会抛此异常。
- ActivitiClassLoadingException:当一个需要被加载的类没有找打或者正当加载的时候报错,就会抛出此异常。
- ActivitiIllegalArgumentException:
- ActivitiTaskAlreadyClamiedException:当一个任务已经被请求,然后调用taskService.clami(...)的时候,会抛出此异常。
像上述一样,和Activiti 引擎互动的方式就是通过org.activiti.engine.ProcessEngine这个类的实例暴露的服务。下面的代码片段假设了你有一个工作中的Activiti环境,你也能获取到一个有效的org.activiti.engine.ProcessEngine。如果你仅仅想要提炼出下面的代码,你能下载或者克隆Activiti unit test template(https://github.com/Activiti/activiti-unit-test-template),把它导入到你的IDE,并且添加一个testUserguideCode()方法到org.activiti.MyUnitTest 单元测试。
3.1 部署流程
通过ResponsitoryService访问的任何数据都与静态数据有关(比如流程定义)。概念地,每一个这样的静态的一部分数据,都是Activiti引擎'respository'的内容。
在src/test/resources/org/activiti/test文件夹中,创建一个新的xml文件,名字为VacationRequest.bpmn20.xml,下面是具体内容。这一节不会解释被用于上面例子中的xml的结构。
<?xml version="1.0" encoding="UTF-8" ?>
<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 id="vacationRequest" name="Vacation request">
<startEvent id="request" activiti:initiator="employeeName">
<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 id="handleRequest" name="Handle vacation request" >
<documentation>
${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>
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>management</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<sequenceFlow id="flow2" sourceRef="handleRequest" targetRef="requestApprovedDecision" />
<exclusiveGateway id="requestApprovedDecision" name="Request approved?" />
<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" />
<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?" />
<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>
<endEvent id="theEnd2" />
</process>
</definitions> |
要让流程引擎认识我们的这个流程,我们必须先发布它。发布意味着引擎会解析BPMN 2.0xml文件给一些事情执行并且为包含在发布包中的每一个流程定义都会添加一个新的数据库记录。这样的好处是:当流程引擎重启的时候,它会一直所有已经发布的流程:
3.2 开始一个流程实例
在把流程定义部署到流程引擎之后,我们就能启动一个新的流程实例。对于每一个流程定义来说,会有很多典型的流程实例。当流程定义的一个流程实例正在运行期,这个流程定义是'buleprint'。
每一件和流程运行状态的事情,都会被RuntimeService监控。有各种各样的方法去启动一个新的流程实例。在下面的代码片段中,我们用我们在流程定义xml中定义的key去启动一个流程实例。我们也会在流程实例启动的时候,提供一些处理过的变量。因为第一个用户任务的描述会用到他们在它的表达式中。因为他们为一个确定的流程定义中的流程实例赋予意义的时候,这些变量通常会被用到。典型的来说,这些变量是让流程实例区别于其他实例的因素。
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 = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("vacationRequest", variables);
// Verify that we started a new process instance
Log.info("Number of process instances: " + runtimeService.createProcessInstanceQuery().count());
3.3 正在完成的任务
当一个流程开始的时候,第一步会成为一个用户的任务。这是一个必须被系统用户执行的步骤。典型的来说,这样的用户将会有一个'inbox of tasks','inbox of tasks'列出了所有需要被这个用户完成的任务。下面的代码就展现了这样一个查询怎么被执行:
// Fetch all tasks for the management group
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();
for (Task task : tasks) { Log.info("Task available: " + task.getName());}
继续流程实例,我们需要完成这个任务。对于Activiti引擎来说,这意味着你需要这个任务。下面的代码断展示了怎么做的:
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);
流程实例会继续到下一步,在这个例子中,这又会成为第一个布骤,因为这个任务没有被审核过。
3.4 暂停和激活一个流程
暂停一个流程定义是可能的。当一个流程定义被暂停,一个新的流程实例不会被创建(一个异常将会被抛出)。通过RepositoryService去暂停一个流程定义。
repositoryService.suspendProcessDefinitionByKey("vacationRequest");
try {
runtimeService.startProcessInstanceByKey("vacationRequest");
} catch (ActivitiException e) {
e.printStackTrace();
}
重新激活一个流程定义,通常的调用repositoryService.activateProcessDefinitionXXX方法中的一个方法。
暂停一个流程实例也有可能。当暂停的时候,这个流程不会继续下去并且没有计划会被执行。暂停流程实例可以通过调用runtimeService.suspendProcessInstance()方法.再次激活流程实例可以调用runtimeService.activateProcessInstanceXXX方法。
3.5 进一步阅读
四、查询API
从引擎中查询数据有两种方法:查询API和本地查询。查询API允许完全安全的用流畅的API编程。你能添加各种各样的条件到你的查询中并且精确地排序。下面的代码是个例子:
List<Task> tasks = taskService.createTaskQuery() .taskAssignee("kermit") .processVariableValueEquals("orderId", "0815") .orderByDueDate().asc() .list();有时,你需要更强大的查询,用一个OR操作或者约束的查询,用API,你不能明确的实现。对于这种情况,我们介绍本地查询,本地查询允许你写自己的SQL查询.返回类型被你用的查询对象定义,并且数据会被映射到正确的对象.如认为,流程实例...因此,你不得不用在数据库中定义的表和字段的名字作为被映射到数据库的查询。这需要一些关于数据结构的知识,并且它推荐给我们要小心的进行本地查询。表的名字能通过API被重新寻回,以致于尽可能的远离对它的依赖。
List<Task> tasks = taskService.createNativeTaskQuery() .sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T WHERE T.NAME_ = #{taskName}") .parameter("taskName", "gonzoTask") .list(); long count = taskService.createNativeTaskQuery() .sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T1, " + managementService.getTableName(VariableInstanceEntity.class) + " V1 WHERE V1.TASK_ID_ = T1.ID_") .count();
五、表达式
Activiti用UEL去解析表达式。UEL代表统一表达式语言(Unifies Expression Language),并且是EE6的一部分。为了支持在所有环境中最新的UEL的所有特点,我们用JUEL的一个编辑版本。
表达式能被用在java service tasks/execution Listeners/Task Listener和Condition sequence flows。尽管有两种表达式类型,值表达式和方法表达式,Activiti概括了他们,以致于他们都能用在表达式需要的地方。
- 值表达式:值得解析。默认的,所有的进程变量都能被拿来用。所有的spring-beans也能拿来用在表达式中。下面是例子:方法表达式:引用一个有或者没有参数的方法。当引用一个没有参数的方法的时候,在方法名字的后面要确定去添加一个空的括号。传过来的参数可以是被他们自己解析的字符串或者表达式。举例:
-
${myVar} ${myBean.myProperty}
-
-
-
${printer.print()} ${myBean.addNewOrder('orderName')} ${myBean.doSomething(myVar, execution)}
-
在所有进程变量中最上面,有一小部分默认的对象变量是被用到表达式中的:
- execution:
- task:
- authenticatedUserId:
流程引擎是线程安全的类并且能被很容易的在多个线程中分享。在一个web应用中,这意味着当一个容器启动或者当一个容器关闭的时候,马上去创建一个流程引擎是可能的。
下面的代码片段,就展示了你能怎么写一个简单的ServletContextListener,在一个纯粹的Servlet环境中去初始化和销毁流程引擎:
public class ProcessEnginesServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
ProcessEngines.init();
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ProcessEngines.destroy();
}
}
contextInitialized方法会委托ProcessEngines.init()方法。它会去扫描activiti.cfg.xml文件,并且根据配置信息去创建一个流程引擎。如果你有多个这样的资源文件,你要确保他们都有不同的名字。 当流程引擎是需要的时候,它能用下面的方法取到。
当然,用任何一个创建流程引擎的变体方法中也是可能的。想配置信息中描述的那样。contextDestryed方法会委托ProcessEngines.destroy()。这样,所有初始化的流程引擎都会关闭。
ProcessEngines.getDefaultProcessEngine()
or
ProcessEngines.getProcessEngine("myName");
当然,用任何一个创建流程引擎的变体方法中也是可能的。想配置信息中描述的那样。contextDestryed方法会委托ProcessEngines.destroy()。这样,所有初始化的流程引擎都会关闭。