五、與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" >
發布過后在數據庫中保存的數據就是這樣:
id | key | name | version |
---|---|---|---|
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,再次發布數據庫數據就成這樣子:
id | key | name | version |
---|---|---|---|
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();