工作流引擎详解!工作流开源框架ACtiviti的详细配置以及安装和使用

2022-01-22 10:10:11 浏览数 (1)

创建ProcessEngine

  • Activiti流程引擎的配置文件是名为activiti.cfg.xml的XML文件.注意与使用Spring方式创建流程引擎是不一样的
  • 使用org.activiti.engine.ProcessEngines类,获得ProcessEngine:
代码语言:javascript复制
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine()

它会在classpath下搜索activiti.cfg.xml,并基于这个文件中的配置构建引擎

代码语言:javascript复制
<beans xmlns="http://www.springframework.org/schema/beans"
       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">

  <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">

    <property name="jdbcUrl" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
    <property name="jdbcDriver" value="org.h2.Driver" />
    <property name="jdbcUsername" value="sa" />
    <property name="jdbcPassword" value="" />

    <property name="databaseSchemaUpdate" value="true" />

    <property name="jobExecutorActivate" value="false" />

    <property name="mailServerHost" value="mail.my-corp.com" />
    <property name="mailServerPort" value="5025" />
  </bean>

</beans>

配置文件中使用的ProcessEngineConfiguration可以通过编程方式创建,可以配置不同的bean id

代码语言:javascript复制
ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault();
ProcessEngineConfiguration.createProcessEngineConfigurationFromResource(String resource);
ProcessEngineConfiguration.createProcessEngineConfigurationFromResource(String resource, String beanName);	// 配置不同的bean id
ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(InputStream inputStream);
ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(InputStream inputStream, String beanName);

如果不使用配置文件进行配置,就会基于默认创建配置

  • ProcessEngineConfiguration.createXXX() 方法都会返回ProcessEngineConfiguration,后续可以调整成所需的对象. 在调用buildProcessEngine()后, 就会创建一个ProcessEngine:
代码语言:javascript复制
  ProcessEngine processEngine = ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration()
  .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_FALSE)
  .setJdbcUrl("jdbc:h2:mem:my-own-db;DB_CLOSE_DELAY=1000")
  .setJobExecutorActivate(true)
  .buildProcessEngine();

ProcessEngineConfiguration bean

  • activiti.cfg.xml必须包含一个id='processEngineConfiguration' 的bean
代码语言:javascript复制
 <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">

这个bean会用来构建ProcessEngine. 有多个类可以用来定义processEngineConfiguration. 这些类对应不同的环境,并设置了对应的默认值:

  • org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration: 单独运行的流程引擎.Activiti会自己处理事务.默认数据库只在引擎启动时检测(如果没有Activiti的表或者表结构不正确就会抛出异常)
  • org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration: 单元测试时的辅助类.Activiti会自己控制事务. 默认使用H2内存数据库,数据库表会在引擎启动时创建,关闭时删除.使用它时,不需要其他配置(除非使用job执行器或邮件功能)
  • org.activiti.spring.SpringProcessEngineConfiguration: 在Spring环境下使用流程引擎
  • org.activiti.engine.impl.cfg.JtaProcessEngineConfiguration: 单独运行流程引擎,并使用JTA事务

数据库配置

定义数据库配置参数

  • 基于数据库配置参数定义数据库连接配置
    • jdbcUrl: 数据库的JDBC URL
    • jdbcDriver: 对应不同数据库类型的驱动
    • jdbcUsername: 连接数据库的用户名
    • jdbcPassword: 连接数据库的密码
  • 基于JDBC参数配置的数据库连接 会使用默认的MyBatis连接池,配置MyBatis连接池:
    • jdbcMaxActiveConnections: 连接池中处于被使用状态的连接的最大值.默认为10
    • jdbcMaxIdleConnections: 连接池中处于空闲状态的连接的最大值
    • jdbcMaxCheckoutTime: 连接被取出使用的最长时间,超过时间会被强制回收. 默认为20000(20秒)
    • jdbcMaxWaitTime: 这是一个底层配置,让连接池可以在长时间无法获得连接时, 打印一条日志,并重新尝试获取一个连接.(避免因为错误配置导致沉默的操作失败) 默认为20000(20秒)

使用javax.sql.DataSource配置

  • Activiti的发布包中没有这些类, 要把对应的类放到classpath下
代码语言:javascript复制
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" >
  <property name="driverClassName" value="com.mysql.jdbc.Driver" />
  <property name="url" value="jdbc:mysql://localhost:3306/activiti" />
  <property name="username" value="activiti" />
  <property name="password" value="activiti" />
  <property name="defaultAutoCommit" value="false" />
</bean>

<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">

    <property name="dataSource" ref="dataSource" />
    ...
</bean>
  • 无论使用JDBC还是DataSource,都可以设置下面的配置:
    • databaseType:
      • 一般不用设置,因为可以自动通过数据库连接的元数据获取
      • 只有自动检测失败时才需要设置.可能的值有:{h2,mysql,oracle,postgres,mssql,db2}
      • 如果没使用默认的H2数据库就必须设置这项.这个配置会决定使用哪些创建/删除脚本和查询语句
    • databaseSchemaUpdate: 设置流程引擎启动和关闭时如何处理数据库表
      • false:默认, 检查数据库表的版本和依赖库的版本,如果版本不匹配就抛出异常
      • true: 构建流程引擎时,执行检查,如果需要就执行更新. 如果表不存在,就创建
      • create-drop: 构建流程引擎时创建数据库表,关闭流程引擎时删除这些表

JNDI数据库配置

  • 在默认情况下,Activiti的数据库配置会放在web应用的WEB-INF/classes目录下的db.properties文件中. 这样做比较繁琐,因为要用户在每次发布时,都修改Activiti源码中的db.properties并重新编译war文件,或者解压缩war文件,修改其中的db.properties
  • 使用 JNDI(Java命名和目录接口) 来获取数据库连接,连接是由servlet容器管理的,可以在war部署外边管理配置. 与db.properties相比,它也允许对连接进行更多的配置

JNDI的使用

  • Activiti Explorer和Activiti Rest应用从db.properties转换为使用JNDI数据库配置:
    • 需要打开原始的Spring配置文件:
      • activiti-webapp-explorer/src/main/webapp/WEB-INF/activiti-standalone-context.xml
      • activiti-webapp-rest2/src/main/resources/activiti-context.xml
    • 删除dbPropertiesdataSource两个bean,然后添加如下bean:
代码语言:javascript复制
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/activitiDB"/>
</bean>
  • 我们需要添加包含了默认的H2配置的context.xml文件
  • 如果已经有了JNDI配置,会覆盖这些配置.对应的配置文件activiti-webapp-explorer2/src/main/webapp/META-INF/context.xml:
代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/activiti-explorer2">
    <Resource auth="Container"
              name="jdbc/activitiDB"
              type="javax.sql.DataSource"
              scope="Shareable"
              description="JDBC DataSource"
              url="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000"
              driverClassName="org.h2.Driver"
              username="sa"
              password=""
              defaultAutoCommit="false"
              initialSize="5"
              maxWait="5000"
              maxActive="120"
              maxIdle="5"/>
</Context>
  • 如果是Activiti REST应用,则添加activiti-webapp-rest2/src/main/webapp/META-INF/context.xml:
代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/activiti-rest2">
    <Resource auth="Container"
              name="jdbc/activitiDB"
              type="javax.sql.DataSource"
              scope="Shareable"
              description="JDBC DataSource"
              url="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=-1"
              driverClassName="org.h2.Driver"
              username="sa"
              password=""
              defaultAutoCommit="false"
              initialSize="5"
              maxWait="5000"
              maxActive="120"
              maxIdle="5"/>
</Context>
  • 最后删除Activiti Explorer和Activiti Rest两个应用中不再使用的db.properties文件

JNDI的配置

  • JNDI数据库配置会因为使用的Servlet container不同而不同
  • Tomcat容器中的JNDI配置如下:
    • JNDI资源配置在CATALINA_BASE/conf/[enginename]/[hostname]/[warname].xml(对于Activiti Explorer来说,通常是在CATALINA_BASE/conf/Catalina/localhost/activiti-explorer.war) 当应用第一次发布时,会把这个文件从war中复制出来.所以如果这个文件已经存在了,需要替换它.修改JNDI资源让应用连接mysql而不是H2:
代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
    <Context antiJARLocking="true" path="/activiti-explorer2">
        <Resource auth="Container"
            name="jdbc/activitiDB"
            type="javax.sql.DataSource"
            description="JDBC DataSource"
            url="jdbc:mysql://localhost:3306/activiti"
            driverClassName="com.mysql.jdbc.Driver"
            username="sa"
            password=""
            defaultAutoCommit="false"
            initialSize="5"
            maxWait="5000"
            maxActive="120"
            maxIdle="5"/>
    </Context>

Activiti支持的数据库

  • h2: 默认配置的数据库
  • mysql
  • oracle
  • postgres
  • db2
  • mssql

创建数据库表

  • 创建数据库表的方法:
    • activiti-engine的jar放到classpath下
    • 添加对应的数据库驱动
    • 把Activiti配置文件(activiti.cfg.xml)放到classpath下,指向你的数据库
    • 执行DbSchemaCreate类的main方法
代码语言:javascript复制
SQL DDL语句可以从Activiti下载页或Activiti发布目录里找到,在database子目录下.
脚本也包含在引擎的jar中:activiti-engine-x.jar在org/activiti/db/create包下,drop目录里是删除语句

- SQL文件的命名方式如下:
[activiti.{db}.{create|drop}.{type}.sql]
type 是:
- engine:引擎执行的表,必须
- identity:包含用户,群组,用户与组之间的关系的表.这些表是可选的,只有使用引擎自带的默认身份管理时才需要
- history:包含历史和审计信息的表,可选的.历史级别设为none时不会使用. 注意这也会引用一些需要把数据保存到历史表中的功能

数据库表名理解

  • Activiti的表都以ACT_开头, 第二部分是表示表的用途的两个字母标识.用途和服务的API对应
    • ACT_RE_*: RE表示repository. 这个前缀的表包含了流程定义和流程静态资源
    • ACT_RU_*: RU表示runtime. 这些是运行时的表,包含流程实例,任务,变量,异步任务等运行中的数据. Activiti只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录.这样运行时表可以一直很小速度很快
    • ACT_ID_*: ID 表示identity. 这些表包含身份信息. 比如用户,组等等
    • ACT_HI_*: HI 表示history. 这些表包含历史数据. 比如历史流程实例, 变量,任务等等
    • ACT_GE_*: 通用数据. 用于不同场景下

数据库升级

  • 在执行更新之前要先使用数据库的备份功能备份数据库
  • 默认情况下,每次构建流程引擎时都会进行版本检测.这一切都在应用启动或Activiti webapp启动时发生.如果Activiti发现数据库表的版本与依赖库的版本不同,就会抛出异常
  • 对activiti.cfg.xml配置文件进行配置来升级:
代码语言:javascript复制
<beans ... >

  <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
    <!-- ... -->
    <property name="databaseSchemaUpdate" value="true" />
    <!-- ... -->
  </bean>

</beans>
  • 然后,把对应的数据库驱动放到classpath里.升级应用的Activiti依赖,启动一个新版本的Activiti指向包含旧版本的数据库,将databaseSchemaUpdate设置为true,Activiti会自动将数据库表升级到新版本
  • 当发现依赖和数据库表版本不通过时,也可以执行更新升级DDL语句
  • 也可以执行数据库脚本,可以在Activiti下载页找到

启用Job执行器

  • JobExecutor是管理一系列线程的组件,可以触发定时器(包含后续的异步消息).
  • 在单元测试场景下,很难使用多线程.因此API允许查询Job(ManagementService.createJobQuery)执行Job (ManagementService.executeJob),
  • 因此Job可以在单元测试中控制, 要避免与job执行器冲突,可以关闭它
  • 默认,JobExecutor在流程引擎启动时就会激活. 如果不想在流程引擎启动后自动激活JobExecutor,可以设置
代码语言:javascript复制
<property name="jobExecutorActivate" value="false" />

配置邮件服务器

  • Activiti支持在业务流程中发送邮件,可以在配置中配置邮件服务器
  • 配置SMTP邮件服务器来发送邮件

配置历史存储

  • Activiti可以配置来定制历史存储信息
代码语言:javascript复制
<property name="history" value="audit" />

表达式和脚本暴露配置

  • 默认情况下,activiti.cfg.xml和Spring配置文件中所有bean 都可以在表达式和脚本中使用
  • 如果要限制配置文件中的bean的可见性,可以通过配置流程引擎配置的beans来配置
  • ProcessEngineConfiguration的beans是一个map.当指定了这个参数,只有包含这个map中的bean可以在表达式和脚本中使用.通过在map中指定的名称来决定暴露的bean

配置部署缓存

  • 因为流程定义的数据是不会改变的,为了避免每次使用访问数据库,所有流程定义在解析之后都会被缓存
  • 默认情况下,不会限制这个缓存.如果想限制流程定义缓存,可以添加如下配置
代码语言:javascript复制
<property name="processDefinitionCacheLimit" value="10" />

这个配置会把默认的HashMap缓存替换成LRU缓存来提供限制. 这个配置的最佳值跟流程定义的总数有关,实际使用中会具体使用多少流程定义也有关

  • 也可以注入自定义的缓存实现,这个bean必须实现org.activiti.engine.impl.persistence.deploy.DeploymentCache接口
代码语言:javascript复制
<property name="processDefinitionCache">
  <bean class="org.activiti.MyCache" />
</property>
  • 类似的配置有knowledgeBaseCacheLimitknowledgeBaseCache, 它们是配置规则缓存的.只有流程中使用规则任务时才用

日志

  • 从Activiti 5.12开始,所有日志(activiti,spring,,mybatis等等)都转发给slf4j允许自定义日志实现
  • 引入Maven依赖log4j实现,需要添加版本
代码语言:javascript复制
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
</dependency>
  • 使用Maven的实例,忽略版本
代码语言:javascript复制
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>jcl-over-slf4j</artifactId>
</dependency>

映射诊断上下文

  • Activiti支持slf4j的MDC功能, 如下的基础信息会传递到日志中记录:
    • 流程定义ID: mdcProcessDefinitionID
    • 流程实例ID: mdcProcessInstanceID
    • 分支ID: mdcexecutionId
  • 默认不会记录这些信息,可以配置日志使用期望的格式来显示它们,扩展通常的日志信息. 比如,通过log4j配置定义会让日志显示上面的信息:
代码语言:javascript复制
 log4j.appender.consoleAppender.layout.ConversionPattern =ProcessDefinitionId=%X{mdcProcessDefinitionID}
executionId=%X{mdcExecutionId}mdcProcessInstanceID=%X{mdcProcessInstanceID} mdcBusinessKey=%X{mdcBusinessKey} %m%n"

当系统进行高风险任务,日志必须严格检查时,这个功能就非常有用,要使用日志分析的情况

事件处理

  • Activiti中实现了一种事件机制,它允许在引擎触发事件时获得提醒
  • 为对应的事件类型注册监听器,在这个类型的任何时间触发时都会收到提醒:
    • 可以添加引擎范围的事件监听器,可以通过配置添加引擎范围的事件监听器在运行阶段使用API
    • 添加event-listener到特定流程定义的BPMN XML中
  • 所有分发的事件,都是org.activiti.engine.delegate.event.ActivitiEvent的子类.事件包含type,executionId,processInstanceId和processDefinitionId. 对应的事件会包含事件发生时对应上下文的额外信息

事件监听器实现

  • 实现事件监听器要实现org.activiti.engine.delegate.event.ActivitiEventListener.
  • 下面监听器的实现会把所有监听到的事件打印到标准输出中,包括job执行的事件异常:
代码语言:javascript复制
public class MyEventListener implements ActivitiEventListener {

  @Override
  public void onEvent(ActivitiEvent event) {
    switch (event.getType()) {

      case JOB_EXECUTION_SUCCESS:
        System.out.println("A job well done!");
        break;

      case JOB_EXECUTION_FAILURE:
        System.out.println("A job has failed...");
        break;

      default:
        System.out.println("Event received: "   event.getType());
    }
  }

  @Override
  public boolean isFailOnException() {
    // The logic in the onEvent method of this listener is not critical, exceptions
    // can be ignored if logging fails...
    return false;
  }
}

isFailOnException(): 决定了当事件分发时onEvent(..) 方法抛出异常时的行为

  • 返回false,会忽略异常
  • 返回true,异常不会忽略,继续向上传播,迅速导致当前命令失败
  • 当事件是一个API调用的一部分时(或其他事务性操作,比如job执行), 事务就会回滚
  • 当事件监听器中的行为不是业务性时,建议返回false
  • activiti提供了一些基础的实现,实现了事件监听器的常用场景可以用来作为基类或监听器实现的样例
    • org.activiti.engine.delegate.event.BaseEntityEventListener:
      • 这个事件监听器的基类可以用来监听实体相关的事件,可以针对某一类型实体,也可以是全部实体
      • 隐藏了类型检测,并提供了三个需要重写的方法:
        • onCreate(..)
        • onUpdate(..)
        • onDelete(..)
        • 当实体创建,更新,或删除时调用
      • 对于其他实体相关的事件,会调用onEntityEvent(..)

事件监听器的配置安装

  • 把事件监听器配置到流程引擎配置中,会在流程引擎启动时激活,并在引擎启动过程中持续工作
  • eventListeners属性需要org.activiti.engine.delegate.event.ActivitiEventListener的队列
    • 通常,我们可以声明一个内部的bean定义,或使用ref引用已定义的bean.下面的代码,向配置添加了一个事件监听器,任何事件触发时都会提醒它,无论事件是什么类型:
代码语言:javascript复制
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
    ...
    <property name="eventListeners">
      <list>
         <bean class="org.activiti.engine.example.MyEventListener" />
      </list>
    </property>
</bean>
  • 为了监听特定类型的事件
    • 可以使用typedEventListeners属性
    • 它需要一个map参数
    • map的key是逗号分隔的事件名或单独的事件名
    • map的value是org.activiti.engine.delegate.event.ActivitiEventListener队列
  • 下面的代码演示了向配置中添加一个事件监听器,可以监听job执行成功或失败:
代码语言:javascript复制
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
    ...
    <property name="typedEventListeners">
      <map>
        <entry key="JOB_EXECUTION_SUCCESS,JOB_EXECUTION_FAILURE" >
          <list>
            <bean class="org.activiti.engine.example.MyJobEventListener" />
          </list>
        </entry>
      </map>
    </property>
</bean>

分发事件的顺序是由监听器添加时的顺序决定的

  • 首先,会调用所有普通的事件监听器(eventListeners属性),按照它们在list中的次序
  • 然后,会调用所有对应类型的监听器(typedEventListeners属性),对应类型的事件被触发

运行阶段添加监听器

  • 通过API:RuntimeService, 在运行阶段添加或删除额外的事件监听器:
代码语言:javascript复制
/**
 * Adds an event-listener which will be notified of ALL events by the dispatcher.
 * @param listenerToAdd the listener to add
 */
void addEventListener(ActivitiEventListener listenerToAdd);

/**
 * Adds an event-listener which will only be notified when an event occurs, which type is in the given types.
 * @param listenerToAdd the listener to add
 * @param types types of events the listener should be notified for
 */
void addEventListener(ActivitiEventListener listenerToAdd, ActivitiEventType... types);

/**
 * Removes the given listener from this dispatcher. The listener will no longer be notified,
 * regardless of the type(s) it was registered for in the first place.
 * @param listenerToRemove listener to remove
 */
 void removeEventListener(ActivitiEventListener listenerToRemove);
  • 运行阶段添加的监听器引擎重启后就消失

流程定义添加监听器

  • 特定流程定义添加监听器:
    • 监听器只会监听与这个流程定义相关的事件以及这个流程定义上发起的所有流程实例的事件
  • 监听器实现:
    • 可以使用全类名定义
    • 引用实现了监听器接口的表达式
    • 配置为抛出一个message,signal,errorBPMN事件
监听器执行自定义逻辑
  • 下面代码为一个流程定义添加了两个监听器:
    • 第一个监听器会接收所有类型的事件,它是通过全类名定义的
    • 第二个监听器只接收作业成功或失败的事件,它使用了定义在流程引擎配置中的beans属性中的一个bean
代码语言:javascript复制
<process id="testEventListeners">
  <extensionElements>
    <activiti:eventListener class="org.activiti.engine.test.MyEventListener" />
    <activiti:eventListener delegateExpression="${testEventListener}" events="JOB_EXECUTION_SUCCESS,JOB_EXECUTION_FAILURE" />
  </extensionElements>
	...
</process>
  • 对于实体相关的事件,也可以设置为针对某个流程定义的监听器,实现只监听发生在某个流程定义上的某个类型实体事件.下面的代码演示了如何实现这种功能:
    • 第一个例子:用于监听所有实体事件
    • 第二个例子:用于监听特定类型的事件
代码语言:javascript复制
<process id="testEventListeners">
  <extensionElements>
    <activiti:eventListener class="org.activiti.engine.test.MyEventListener" entityType="task" />
    <activiti:eventListener delegateExpression="${testEventListener}" events="ENTITY_CREATED" entityType="task" />
  </extensionElements>
  ...
</process>
  • entityType支持的值有:
    • attachment
    • comment
    • execution
    • identity-link
    • job
    • process-instance
    • process-definition
    • task
监听抛出BPMN事件
  • 另一种处理事件的方法是抛出一个BPMN事件:
    • 只针对与抛出一个activiti事件类型的BPMN事件, 抛出一个BPMN事件,在流程实例删除时,会导致一个错误
  • 下面的代码演示了如何在流程实例中抛出一个signal,把signal抛出到外部流程(全局),在流程实例中抛出一个消息事件,在流程实例中抛出一个错误事件.除了使用classdelegateExpression, 还使用了throwEvent属性,通过额外属性,指定了抛出事件的类型
代码语言:javascript复制
<process id="testEventListeners">
  <extensionElements>
    <activiti:eventListener throwEvent="signal" signalName="My signal" events="TASK_ASSIGNED" />
  </extensionElements>
</process>
代码语言:javascript复制
<process id="testEventListeners">
  <extensionElements>
    <activiti:eventListener throwEvent="globalSignal" signalName="My signal" events="TASK_ASSIGNED" />
  </extensionElements>
</process>
代码语言:javascript复制
<process id="testEventListeners">
  <extensionElements>
    <activiti:eventListener throwEvent="message" messageName="My message" events="TASK_ASSIGNED" />
  </extensionElements>
</process>
代码语言:javascript复制
<process id="testEventListeners">
  <extensionElements>
    <activiti:eventListener throwEvent="error" errorCode="123" events="TASK_ASSIGNED" />
  </extensionElements>
</process>
  • 如果需要声明额外的逻辑,是否抛出BPMN事件,可以扩展activiti提供的监听器类:
    • 在子类中重写isValidEvent(ActivitiEvent event), 可以防止抛出BPMN事件.对应的类是:
      • org.activiti.engine.impl.bpmn.helper.MessageThrowingEventListener
      • org.activiti.engine.test.api.event.SignalThrowingEventListenerTest
      • org.activiti.engine.impl.bpmn.helper.ErrorThrowingEventListener
流程定义监听器注意点
  • 事件监听器只能声明在process元素中,作为extensionElements的子元素.监听器不能定义在流程的单个activity下
  • delegateExpression中的表达式无法访问execution上下文,这与其他表达式不同(比如gateway).它只能引用定义在流程引擎配置的beans属性中声明的bean, 或者使用spring(未使用beans属性)中所有实现了监听器接口的spring-bean
  • 使用监听器的class属性时,只会创建一个实例.监听器实现不会依赖成员变量,是多线程安全的
  • 当一个非法的事件类型用在events属性或throwEvent中时,流程定义发布时就会抛出异常(会导致部署失败)
    • 如果class或delegateExecution由问题:类不存在,不存在的bean引用,或代理类没有实现监听器接口
      • 在流程启动时抛出异常
      • 在第一个有效的流程定义事件被监听器接收时
    • 所以要保证引用的类正确的放在classpath下,表达式也要引用一个有效的实例

通过API分发事件

  • Activiti我们提供了通过API使用事件机制的方法,允许触发定义在引擎中的任何自定义事件
  • 建议只触发类型为CUSTOMActivitiEvents.可以通过RuntimeService触发事件:
代码语言:javascript复制
/**
 * Dispatches the given event to any listeners that are registered.
 * @param event event to dispatch.
 *
 * @throws ActivitiException if an exception occurs when dispatching the event or when the {@link ActivitiEventDispatcher}
 * is disabled.
 * @throws ActivitiIllegalArgumentException when the given event is not suitable for dispatching.
 */
 void dispatchEvent(ActivitiEvent event);

支持的事件类型

  • 引擎中每个事件类型都对应org.activiti.engine.delegate.event.ActivitiEventType中的一个枚举值

事件名称

事件描述

事件类型

ENGINE_CREATED

监听器监听的流程引擎已经创建,准备好接受API调用

ActivitiEvent

ENGINE_CLOSED

监听器监听的流程引擎已经关闭,不再接受API调用

ActivitiEvent

ENTITY_CREATED

创建了一个新实体,实体包含在事件中

ActivitiEntityEvent

ENTITY_INITIALIZED

创建了一个新实体,初始化也完成了.如果这个实体的创建会包含子实体的创建,这个事件会在子实体都创建/初始化完成后被触发,这是与ENTITY_CREATED的区别

ActivitiEntityEvent

ENTITY_UPDATED

更新了已存在的实体,实体包含在事件中

ActivitiEntityEvent

ENTITY_DELETED

删除了已存在的实体,实体包含在事件中

ActivitiEntityEvent

ENTITY_SUSPENDED

暂停了已存在的实体,实体包含在事件中.会被ProcessDefinitions,ProcessInstances和Tasks抛出

ActivitiEntityEvent

ENTITY_ACTIVATED

激活了已存在的实体,实体包含在事件中.会被ProcessDefinitions,ProcessInstances和Tasks抛出

ActivitiEntityEvent

JOB_EXECUTION_SUCCESS

作业执行成功,job包含在事件中

ActivitiEntityEvent

JOB_EXECUTION_FAILURE

作业执行失败,作业和异常信息包含在事件中

ActivitiEntityEventActivitiExceptionEvent

JOB_RETRIES_DECREMENTED

因为作业执行失败,导致重试次数减少.作业包含在事件中

ActivitiEntityEvent

TIMER_FIRED

触发了定时器,job包含在事件中

ActivitiEntityEvent

JOB_CANCELED

取消了一个作业.事件包含取消的作业.作业可以通过API调用取消,任务完成后对应的边界定时器也会取消,在新流程定义发布时也会取消

ActivitiEntityEvent

ACTIVITY_STARTED

一个节点开始执行

ActivitiActivityEvent

ACTIVITY_COMPLETED

一个节点成功结束

ActivitiActivityEvent

ACTIVITY_SIGNALED

一个节点收到了一个信号

ActivitiSignalEvent

ACTIVITY_MESSAGE_RECEIVED

一个节点收到了一个消息.在节点收到消息之前触发,收到后,会触发ACTIVITY_SIGNAL或ACTIVITY_STARTED, 这会根据节点的类型:边界事件,事件子流程开始事件

ActivitiMessageEvent

ACTIVITY_ERROR_RECEIVED

一个节点收到了一个错误事件.在节点实际处理错误之前触发, 事件的activityId对应着处理错误的节点.这个事件后续会是ACTIVITY_SIGNALLED或ACTIVITY_COMPLETE, 如果错误发送成功的话

ActivitiErrorEvent

UNCAUGHT_BPMN_ERROR

抛出了未捕获的BPMN错误.流程没有提供针对这个错误的处理器.事件的activityId为空

ActivitiErrorEvent

ACTIVITY_COMPENSATE

一个节点将要被补偿.事件包含了将要执行补偿的节点id

ActivitiActivityEvent

VARIABLE_CREATED

创建了一个变量.事件包含变量名,变量值和对应的分支或任务(如果存在)

ActivitiVariableEvent

VARIABLE_UPDATED

更新了一个变量.事件包含变量名,变量值和对应的分支或任务(如果存在)

ActivitiVariableEvent

VARIABLE_DELETED

删除了一个变量.事件包含变量名,变量值和对应的分支或任务(如果存在)

ActivitiVariableEvent

TASK_ASSIGNED

任务被分配给了一个人员.事件包含任务

ActivitiEntityEvent

TASK_CREATED

创建了新任务.它位于ENTITY_CREATE事件之后.当任务是由流程创建时,这个事件会在TaskListener执行之前被执行

ActivitiEntityEvent

TASK_COMPLETED

任务完成.它会在ENTITY_DELETE事件之前触发.当任务是流程一部分时,事件会在流程继续运行之前, 后续事件将是ACTIVITY_COMPLETE,对应着完成任务的节点

ActivitiEntityEvent

TASK_TIMEOUT

任务已超时.在TIMER_FIRED事件之后,会触发用户任务的超时事件,当这个任务分配了一个定时器的时候

ActivitiEntityEvent

PROCESS_COMPLETED

流程已结束.在最后一个节点的ACTIVITY_COMPLETED事件之后触发.当流程到达的状态,没有任何后续连线时,流程就会结束

ActivitiEntityEvent

MEMBERSHIP_CREATED

用户被添加到一个组里.事件包含了用户和组的id

ActivitiMembershipEvent

MEMBERSHIP_DELETED

用户被从一个组中删除.事件包含了用户和组的id

ActivitiMembershipEvent

MEMBERSHIPS_DELETED

所有成员被从一个组中删除.在成员删除之前触发这个事件,所以他们都是可以访问的.因为性能方面的考虑,不会为每个成员触发单独的MEMBERSHIP_DELETED事件

ActivitiMembershipEvent

  • 引擎内部所有ENTITY_* 事件都是与实体相关的,实体事件与实体的对应关系:
    • [ENTITY_CREATED],[ENTITY_INITIALIZED],[ENTITY_DELETED]:
      • Attachment
      • Comment
      • Deployment
      • Execution
      • Group
      • IdentityLink
      • Job
      • Model
      • ProcessDefinition
      • ProcessInstance
      • Task
      • User
    • ENTITY_UPDATED:
      • Attachment
      • Deployment
      • Execution
      • Group
      • IdentityLink
      • Job
      • Model
      • ProcessDefinition
      • ProcessInstance
      • Task
      • User
    • ENTITY_SUSPENDED, ENTITY_ACTIVATED:
      • ProcessDefinition
      • ProcessInstance
      • Execution
      • Task

注意

  • 只有同一个流程引擎中的事件会发送给对应的监听器
  • 如果有很多引擎在同一个数据库运行,事件只会发送给注册到对应引擎的监听器.其他引擎发生的事件不会发送给这个监听器,无论实际上它们运行在同一个或不同的JVM中
  • 对应的事件类型都包含对应的实体.根据类型或事件,这些实体不能再进行更新(比如,当实例以被删除).可能的话,使用事件提供的EngineServices来以安全的方式来操作引擎.即使如此,也要小心的对事件对应的实体进行更新,操作
  • 没有对应历史的实体事件,因为它们都有运行阶段的对应实体

0 人点赞