Activiti工作流学习-----基于5.19.0版本(5)

五、与Spring集成

实际项目中一般都有Spring的身影,与Spring集成使得Activiti的实用性得到提高。activiti和Spring整合需要activiti-spring的jar在类路径下面,整合类是:org.activiti.spring.ProcessEngineFactoryBean,它将加载配置对象和产生流程引擎对象。例如下面代码:

<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
    ...
</bean>

<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
  <property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>

5.1 事务的配置

 在xml配置SpringProcessEngineConfiguration的dataSource的时候,实际上工作流使用的是org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy,它是dataSource的一个代理类,它的作用就是确定从dataSource中获取的数据库连接已经绑定了Spring的事务,所以你无需在进行配置dataSource的代理类,以防失去TransactionAwareDataSourceProxy的效果。

确保在配置中没有配置TransactionAwareDataSourceProxy,因为它在Spring的事务中不会有作用(比如DataSourceTransactionManager和JPATransactionManager不需要代理的dataSource)。

 1 <beans xmlns="http://www.springframework.org/schema/beans"
 2        xmlns:context="http://www.springframework.org/schema/context"
 3        xmlns:tx="http://www.springframework.org/schema/tx"
 4        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5        xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd
 6                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
 7                            http://www.springframework.org/schema/tx      http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
 8 
 9   <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
10     <property name="driverClass" value="org.h2.Driver" />
11     <property name="url" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
12     <property name="username" value="sa" />
13     <property name="password" value="" />
14   </bean>
15 
16   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
17     <property name="dataSource" ref="dataSource" />
18   </bean>
19 
20   <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
21     <property name="dataSource" ref="dataSource" />
22     <property name="transactionManager" ref="transactionManager" />
23     <property name="databaseSchemaUpdate" value="true" />
24     <property name="jobExecutorActivate" value="false" />
25   </bean>
26 
27   <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
28     <property name="processEngineConfiguration" ref="processEngineConfiguration" />
29   </bean>
30 
31   <bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService" />
32   <bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService" />
33   <bean id="taskService" factory-bean="processEngine" factory-method="getTaskService" />
34   <bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService" />
35   <bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" />
36 
37 ...

 在其他的Spring配置文件中配置其他的,这样功能清晰,后期容易维护,例如:

 1 <beans>
 2   ...
 3   <tx:annotation-driven transaction-manager="transactionManager"/>
 4 
 5   <bean id="userBean" class="org.activiti.spring.test.UserBean">
 6     <property name="runtimeService" ref="runtimeService" />
 7   </bean>
 8 
 9   <bean id="printer" class="org.activiti.spring.test.Printer" />
10 
11 </beans>

在Spring中启动容器(application context)的方法有很多,比如下面利用类路径加载配置文件:

1 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(
2     "org/activiti/examples/spring/SpringTransactionIntegrationTest-context.xml");

而在单元测试中,我们可以这样:

1 @ContextConfiguration("classpath:org/activiti/spring/test/transaction/SpringTransactionIntegrationTest-context.xml")

在Spring的IOC容器中我们可以轻易的获取我们需要的工作流的Service,ProcessEngineFactoryBean实现了对这些Service的Propagation.REQUIRED的事务级别的控制,例如下面这段代码:

1 RepositoryService repositoryService =
2   (RepositoryService) applicationContext.getBean("repositoryService");
3 String deploymentId = repositoryService
4   .createDeployment()
5   .addClasspathResource("org/activiti/spring/test/hello.bpmn20.xml")
6   .deploy()
7   .getId();

在其他方面也有同样的效果,在下面这种情况中,Spring的事务是环绕在serBean.hello()中,这时Activiti的Service会加入事务当中。

UserBean userBean = (UserBean) applicationContext.getBean("userBean");
userBean.hello();

而在具体的UserBean中,我们通过前面的配置是注入了RuntimeService:

 1 public class UserBean {
 2 
 3   /** Spring注入 */
 4   private RuntimeService runtimeService;
 5 
 6   @Transactional
 7   public void hello() {
 8     // here you can do transactional stuff in your domain model
 9     // and it will be combined in the same transaction as
10     // the startProcessInstanceByKey to the Activiti RuntimeService
11     runtimeService.startProcessInstanceByKey("helloProcess");
12   }
13 
14   public void setRuntimeService(RuntimeService runtimeService) {
15     this.runtimeService = runtimeService;
16   }
17 }

5.2 Spring中使用表达式

 在Spring的配置中也有像activiti的表达式使用,有可能表达式中的bean有限制,在使用Map配置发现没有bean可用,在SpringProcessEngineConfiguration的beans属性配置Map时,如果里面的bean不存在也会通过,只不过bean为空而已。下面这段代码配置beans属性:

<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
  ...
  <property name="beans">
    <map>
      <entry key="printer" value-ref="printer" />
    </map>
  </property>
</bean>

<bean id="printer" class="org.activiti.examples.spring.Printer" />

现在我们可以在xxxbpmn.xml文件中可以直接使用printer了:

<definitions id="definitions">

  <process id="helloProcess">

    <startEvent id="start" />
    <sequenceFlow id="flow1" sourceRef="start" targetRef="print" />

    <serviceTask id="print" activiti:expression="#{printer.printMessage()}" />
    <sequenceFlow id="flow2" sourceRef="print" targetRef="end" />

    <endEvent id="end" />

  </process>

</definitions>

而我们的Printer类代码:

public class Printer {

  public void printMessage() {
    System.out.println("hello world");
  }
}

在Spring中配置:

<beans>
  ...

  <bean id="printer" class="org.activiti.examples.spring.Printer" />

</beans>

5.3 使用Spring实现自动发布

 自动发布流程定义是整合Spring后的功能,在流程引擎被创建的时候就会将spring配置的资源加载发布。为了防止重复发布,整合的jar中添加了过滤功能,只有加载的资源真正的被改变,才会重新发布。好处在于一些场景(junit 测试)下,Spring容器经常重启。

例如下面配置,其中deploymentResources属性设置资源路径。

<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
  ...
  <property name="deploymentResources"
    value="classpath*:/org/activiti/spring/test/autodeployment/autodeploy.*.bpmn20.xml" />
</bean>

<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
  <property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>

在上面的配置当中,配置路径的所有资源将会进行匹配,然后将其资源发布到流程引擎中去。这种过滤手段一定程度上避免了一个资源文件的修改引发所有的流程定义资源文件再次发布的可能性。在一些场景并不会这样,例如你发布的资源中仅仅其中一个是修改的,但是发布是将所有过滤后的都发布出去。activiti提供了自定义发布的功能,在bean节点:SpringProcessEngineConfiguration添加属性deploymentMode,目前它有三种取值策略:

  • default:没有定义deploymentMode时,系统默认设置为default.
  • single-resource:每个资源文件单独发布,如果资源文件改变,单独发布。
  • resource-parent-folder:每个资源文件夹单独发布,也就是说在不同文件夹下的资源文件将会以不同的文件夹为单位,进行防重复发布功能的发布。它介于default(批量发布)和single-resource(单个发布)之间。

以上就是所有的deploymentMode取值,如果你觉得不能够满足你的需求,你可以继承SpringProcessEngineConfiguration,重写其中的getAutoDeploymentStrategy(String deploymentMode)方法实现自己的功能。

5.4 在Spring环境的单元测试

 前面介绍过activiti自己提供的测试方法,而在Spring中进行单元测试的话,可以这样:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:org/activiti/spring/test/junit4/springTypicalUsageTest-context.xml")
public class MyBusinessProcessTest {

  @Autowired
  private RuntimeService runtimeService;

  @Autowired
  private TaskService taskService;

  @Autowired
  @Rule
  public ActivitiRule activitiSpringRule;

  @Test
  @Deployment
  public void simpleProcessTest() {
    runtimeService.startProcessInstanceByKey("simpleProcess");
    Task task = taskService.createTaskQuery().singleResult();
    assertEquals("My Task", task.getName());

    taskService.complete(task.getId());
    assertEquals(0, runtimeService.createProcessInstanceQuery().count());

  }
}

不过值得注意的是你测试之前需要将org.activiti.engine.test.ActivitiRule配置注册到Spring的文件中,它会在上述例子中自动注入到测试中。

<bean id="activitiRule" class="org.activiti.engine.test.ActivitiRule">
  <property name="processEngine" ref="processEngine" />
</bean>

目前本人使用Spring-Boot不多(就没怎么用),至于Spring-Boot和Activiti的集成,感兴趣的同学可以自己去了解,反正我觉得太过于封装的东西不要太过于追求。

六、 工作流动态发布

将流程定义等资源文件以zip压缩格式打包后,activiti引擎将会去扫描含有.bpmn20.xml 或者 .bpmn扩展名的文件,这些文件将会被识别解析。但注意不要将java文件以及编译后文件也加到打包文件中,流程引擎只会加载当前类路径下面的类文件,同时加到打包文件的class文件也不会被加到类路径下面去。

这时后台需要代码实现发布功能,比如这样:

String barFileName = "path/to/process-one.bar";
ZipInputStream inputStream = new ZipInputStream(new FileInputStream(barFileName));

repositoryService.createDeployment()
    .name("process-one.bar")
    .addZipInputStream(inputStream)
    .deploy();

在官方提供的Activiti Explorer项目中也有动态发布功能集成,将其项目部署到tomcat后,我使用了管理员Kermit登录系统,点击管理-->部署包--->添加新的部署包

弹出一个上传文件的弹出框:

 

 而我们资源打包文件引用到的类需要放到流程引擎的classpath下面.

6.1 流程定义的版本

BPMN是没有版本的概念的,bpmn文件交给版本控制软件(比如SVN,git,Mercurial)进行管理,这样子设计方便我们对项目的集中管理,而存入数据库的流程定义的版本是工作流在发布期间标识的版本。每一个流程定义写进数据库需要初始化这些属性:key, version, name 和id。

id的值来自流程定义文件的key值。name值来自于流程定义文件的name值,第一次发布默认version是1,随后的发布会将版本递增。id的取值是{流程定义的key值}:{流程定义的版本}:{随机数},而这个随机数在分布式环境中也是保证是唯一的。

比如下面这个流程定义文件:

1 <definitions id="myDefinitions" >
2   <process id="myProcess" name="My important process" >

发布过后在数据库中保存的数据就是这样:

idkeynameversion

myProcess:1:676

myProcess

My important process

1

假使我们重新发布了流程定义文件,但文件中id的值并没有修改,数据库中的数据变成了这样子:

myProcess:1:676

myProcess

My important process

1

myProcess:2:870

myProcess

My important process

2

如果我们修改了id,再次发布数据库数据就成这样子:

idkeynameversion

myProcess:1:676

myProcess

My important process

1

myProcess:2:870

myProcess

My important process

2

myNewProcess:1:1033

myNewProcess

My important process

1

可以看出发布区分新的流程定义会根据key进行判断,Activiti仅仅会根据key值的不同进行区分,所以流程版本变成了1。

6.2 添加流程图发布

发布过程中可以添加流程图进去, 流程图会被视为资源而保存起来,流程图主要用在Activiti Explorer的页面中显示给用户查看。假定我们有一个流程的xml文件在类路径下面,activiti使用命名规则进行获取流程图:

  • 如果流程图已经存在并且使用流程定义文件名作为图片名称,当然后缀名是git、jpg、png都可以,将会被获取加载,如果有其他名称的图片文件,将会被忽视。
  • 如果图片文件在当前流程定义文件的同一个目录里面不存在。

人工添加流程图代码实现就是如下所示:

1 repositoryService.createDeployment()
2   .name("expense-process.bar")
3   .addClasspathResource("org/activiti/expenseProcess.bpmn20.xml")
4   .addClasspathResource("org/activiti/expenseProcess.png")
5   .deploy();

取出图片资源代码实现:

1 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
2   .processDefinitionKey("expense")
3   .singleResult();
4 
5 String diagramResourceName = processDefinition.getDiagramResourceName();
6 InputStream imageStream = repositoryService.getResourceAsStream(
7     processDefinition.getDeploymentId(), diagramResourceName);

如果bpmn文件中含有必要的信息,activiti将会生成流程图片,如果不想生成可以设置:

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

bpmn文件可以被用户进行自定义分类,帮助用户自己识别使用,在bpmn文件的:<definitions …​ targetNamespace="yourCategory" 可以设置。

如果使用代码实现这个功能可以这样:

1 repositoryService
2     .createDeployment()
3     .category("yourCategory")
4     ...
5     .deploy();

 

posted on 2016-08-21 23:11  liujie037  阅读(2159)  评论(0编辑  收藏  举报

导航