Chapter 4. Spring integration(Spring集成)
Table of Contents
- ProcessEngineFactoryBean
- Transactions(事务)
- Expressions(表达式)
- Automatic resource deployment(自动资源部署)
- Unit testing(单元测试)
While you definitely can use Activiti without Spring, we've provided some very nice integration features that are explained in this chapter.
尽管你肯定能够不用使用Spring而使用Activiti,但是本章将解释一些优秀的Spring集成特性。
ProcessEngineFactoryBean
The ProcessEngine
can be configured as a regular Spring bean. The starting point of the integration is the class org.activiti.spring.ProcessEngineFactoryBean
. That bean takes a process engine configuration and creates the process engine. This means that the way and all configuration properties documented in the configuration section are exactly the same as for Spring:
ProcessEngine
能够配置为一个普通的Spring bean。集成的开始点是类org.activiti.spring.ProcessEngineFactoryBean。那个bean提取一个流程并建立流程引擎。这意味着方法和所有在配置部分的归档的配置属性,完全和Spring相同:
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
...
</bean>
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>
Do note that the processEngineConfiguration
bean now uses the org.activiti.spring.SpringProcessEngineConfiguration
class.
一定要注意:processEngineConfiguration bean现在使用org.activiti.spring.SpringProcessEngineConfiguration
类。
Transactions(事务)
We'll explain the SpringTransactionIntegrationTest
found in the spring examples of the distribution step by step. Here is the spring configuration file that we use in this example (located in SpringTransactionIntegrationTest-context.xml). The quoted section contains the dataSource, transactionManager, processEngine and the Activiti Engine services.
我们将一步一步地解释在发行包中的Spring示例的SpringTransactionIntegrationTest
。这里是这个示例里所使用的配置文件(位置在SpringTransactionIntegrationTest-context.xml)。引号部分包含了数据源,事务管理器,流程引擎和Activiti引擎服务。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<property name="targetDataSource">
<bean class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="driverClass" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<property name="databaseType" value="h2" />
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseSchemaUpdate" value="true" />
<property name="jobExecutorActivate" value="false" />
</bean>
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>
<bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService" />
<bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService" />
<bean id="taskService" factory-bean="processEngine" factory-method="getTaskService" />
<bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService" />
<bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" />
...
The remainder of that spring configuration file contains the beans and configuration that we'll use in this particular example:
Spring配置文件的其余部分包含了bean和在这个示例所使用的配置:
<beans>
...
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="userBean" class="org.activiti.spring.test.UserBean">
<property name="runtimeService" ref="runtimeService" />
</bean>
<bean id="printer" class="org.activiti.spring.test.Printer" />
</beans>
First the application context is created with any of the Spring ways to do that. In this example you could use a classpath XML resource to configure our Spring application context:
首先,用任何Spring方式建立应用程序上下文。在本例,能够使用一个classpath XML 资源来配置我们的Spring应用程序上下文:
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("org/activiti/examples/spring/SpringTransactionIntegrationTest-context.xml");
or since it is a test:
或者它是一个测试:
@ContextConfiguration("classpath:org/activiti/spring/test/transaction/SpringTransactionIntegrationTest-context.xml")
Then we can get the service beans and invoke methods on them. The ProcessEngineFactoryBean will have added an extra interceptor to the services that applies Propagation.REQUIRED transaction semantics on the Activiti service methods. So we can use for example the repositoryService to deploy a process like this:
然后我们能够得到服务bean并调用它们之上的方法。将对一个应用繁殖的,在Activiti服务方法之上必要的事务语义服务增加一个额外的拦截器。所以我们能够像下面来部署一个流程来使用 repositoryService:
RepositoryService repositoryService = (RepositoryService) applicationContext.getBean("repositoryService");
String deploymentId = repositoryService
.createDeployment()
.addClasspathResource("org/activiti/spring/test/hello.bpmn20.xml")
.deploy()
.getId();
The other way around also works. In this case, the Spring transaction will be around the userBean.hello() method and the Activiti service method invocation will join that same transaction.
其它方法也可工作。在这种情况下, Spring事务将包围在方法附近。Activiti服务方法调用将加入同一事务。
UserBean userBean = (UserBean) applicationContext.getBean("userBean");
userBean.hello();
The UserBean looks like this. Remember from above in the Spring bean configuration we injected the repositoryService into the userBean.
UserBean看起来如此。记得吗,在上面的Spring bean配置里,已将repositoryService注入到userBean。
public class UserBean {
/** injected by Spring */
private RuntimeService runtimeService;
@Transactional
public void hello() {
// here you can do transactional stuff in your domain model
// and it will be combined in the same transaction as
// the startProcessInstanceByKey to the Activiti RuntimeService
runtimeService.startProcessInstanceByKey("helloProcess");
}
public void setRuntimeService(RuntimeService runtimeService) {
this.runtimeService = runtimeService;
}
}
Expressions(表达式)
When using the ProcessEngineFactoryBean, by default, all expressions in the BPMN processes will also 'see' the Spring beans. For example, the SpringTransactionIntegrationTest hello.bpmn20.xml
shows how a method on a Spring bean can be invoked using a UEL method expression:
当使用ProcessEngineFactoryBean时, 缺省地,在BPMN流程的所有表达式也将看见Spring beans。例如,SpringTransactionIntegrationTest hello.bpmn20.xml
展示了如何能用一个UEL方法表达式调用 在Spring bean上一个方法。
<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>
Where Printer
looks like this:
这里 Printer
看起来如下:
public class Printer {
public void printMessage() {
System.out.println("hello world");
}
}
And the Spring bean configuration (also shown above) looks like this:
Spring bean配置(如上所示)看起来如下:
<beans ...>
...
<bean id="printer" class="org.activiti.examples.spring.Printer" />
</beans>
Automatic resource deployment(自动资源部署)
Spring integration also has a special feature for deploying resources. In the process engine configuration, you can specify a set of resources. When the process engine is created, all those resources will be scanned and deployed. There is filtering in place that prevents duplicate deployments. Only when the resources actually have changed, will new deployments be deployed to the Activiti DB. This makes sense in a lot of use case, where the Spring container is rebooted often (eg testing).
Spring集成也为部署资源提供一个特殊的特性。在流程引擎配置里,你能指定一系列资源。当建立资源引擎时,将扫描并部署所有这些资源。存在一个防止复制部署的过滤器。只有当资源实际上已经发生变化时,才将新的部署才部署到Activiti 数据库里。这个需要Spring容器需要经常重新引导(比如测试)的地方才有意义。
Here's an example
这里有个示例
<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>
Unit testing(单元测试)
When integrating with Spring, business processes can be tested very easily using the standard Activiti testing facilities. Following example shows how a business process is tested in a typical Spring-based unit test:
当和Spring集成时,采用标准的测试设施Activiti testing facilities让业务流程的测试轻而易举。下例展示在一个典型基于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());
}
}
Note that for this to work, you need to define a org.activiti.engine.test.ActivitiRule bean in the Spring configuration (which is injected by auto-wiring in the example above).
注意:为了让这个能够工作,需要在Spring配置里面 定义一个org.activiti.engine.test.ActivitiRule bean(在上例里通过auto-wiring 注入)。
<bean id="activitiRule" class="org.activiti.engine.test.ActivitiRule">
<property name="processEngine" ref="processEngine" />
</bean>