天天看點

Spring內建使用工作流架構Activiti!事務配置,注解配置以及流程資源自動化部署ProcessEngineFactoryBean事務表達式資源自動部署單元測試基于注解的配置JPA和Hibernate

Spring使用Activiti提供了一些非常不錯的內建特性,隻在Activiti與Spring內建時使用

ProcessEngineFactoryBean

  • 可以把流程引擎(ProcessEngine)作為一個普通的Spring bean進行配置
  • 類org.activiti.spring.ProcessEngineFactoryBean是內建的切入點,這個bean需要一個流程引擎配置來建立流程引擎
  • Spring內建的配置和流程引擎bean,使用的processEngineConfiguration bean是 org.activiti.spring.SpringProcessEngineConfiguration類:
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
    ...
</bean>

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

事務

  • 使用這個例子的Spring配置檔案SpringTransactionIntegrationTest-context.xml:
    • dataSource: 資料源
    • transactionManager: 事務管理器
    • processEngine: 流程引擎
    • Activiti引擎服務
  • 當把資料源(DataSource)傳遞給SpringProcessEngineConfiguration(使用"dataSource"屬性)之後,Activiti内部使用了一個org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy代理來封裝傳遞進來的資料源(DataSource).這樣做是為了確定從資料源(DataSource)擷取的SQL連接配接能夠與Spring的事物結合在一起發揮得更出色.這意味不需要在Spring配置中代理資料源(dataSource). 但是仍然允許傳遞一個TransactionAwareDataSourceProxy到SpringProcessEngineConfiguration中
  • 為了確定在Spring配置中聲明的一個TransactionAwareDataSourceProxy,不能把使用它的應用交給Spring事物控制的資源(例如DataSourceTransactionManager和JPATransactionManager需要非代理的資料源)
<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.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>

  <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="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" />

...           
  • Spring配置檔案的其餘部分包含beans和在特有的例子中的配置:
<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>           
  • 首先使用任意的一種Spring建立應用上下文的方式建立其Spring應用上下文.可以使用類路徑下面的XML資源來配置我們的Spring應用上下文:
ClassPathXmlApplicationContext applicationContext =
    new ClassPathXmlApplicationContext("org/activiti/examples/spring/SpringTransactionIntegrationTest-context.xml");           

或者

@ContextConfiguration("classpath:org/activiti/spring/test/transaction/SpringTransactionIntegrationTest-context.xml")           
  • 然後我們就可以得到Activiti的服務beans并且調用該服務上面的方法,ProcessEngineFactoryBean将會對該服務添加一些額外的攔截器,在Activiti服務上面的方法使用的是Propagation.REQUIRED事物語義. 可以使用repositoryService去部署一個流程:
RepositoryService repositoryService = (RepositoryService) applicationContext.getBean("repositoryService");
String deploymentId = repositoryService
  .createDeployment()
  .addClasspathResource("org/activiti/spring/test/hello.bpmn20.xml")
  .deploy()
  .getId();              
  • 其他相同的服務也是同樣可以這麼使用.在這個例子中,Spring的事務将會圍繞在userBean.hello()上,并且調用Activiti服務的方法也會加入到這個事務中
UserBean userBean = (UserBean) applicationContext.getBean("userBean");
userBean.hello();           
  • 在上面Spring bean的配置中把repositoryService注入到userBean中
public class UserBean {

  /** 由Spring注入 */
  private RuntimeService runtimeService;

  @Transactional
  public void hello() {
        //這裡,你可以在你們的領域模型中做一些事物處理。
        //當在調用Activiti RuntimeService的startProcessInstanceByKey方法時,
        //它将會結合到同一個事物中。
    runtimeService.startProcessInstanceByKey("helloProcess");
  }

  public void setRuntimeService(RuntimeService runtimeService) {
    this.runtimeService = runtimeService;
  }
}           

表達式

  • 當使用ProcessEngineFactoryBean時候,預設情況下,在BPMN流程中的所有表達式都将會"看見"所有的Spring beans. 可以限制在表達式中暴露出的beans或者甚至可以在配置中使用一個Map不暴露任何beans
  • 想要不暴露任何beans,隻需要在SpringProcessEngineConfiguration中傳遞一個空的list作為'beans'的屬性. 當不設定'beans'的屬性時,在應用上下文中Spring beans都是可以使用的
  • 下面的例子暴露了一個單例bean(printer),可以把"printer"當作關鍵字使用:
<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" />           
  • 現在暴露出來的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>           
  • 這裡的Printer類似這樣:
public class Printer {

  public void printMessage() {
    System.out.println("hello world");
  }
}           
  • Spring bean的配置類似這樣:
<beans ...>
  ...

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

</beans>           

資源自動部署

  • Spring的內建有專門針對對資源部署的特性
  • 在流程引擎的配置中,可以指定一組資源,當流程引擎被建立的時候,所有在這裡的資源都将會被自動掃描與部署
  • 在這裡有過濾以防止資源重新部署,隻有當這個資源真正發生改變的時候,它才會向Activiti使用的資料庫建立新的部署.
  • 這對于很多用例來說,當Spring容器經常重新開機的情況下,使用是非常不錯的選擇
<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引擎的一個單獨釋出包下.用來檢測防止未修改資源重複釋出的機制會作用到整個釋出包中

有時候,這可能不是想要的.比如,如果你釋出了很多流程資源,但是隻修改裡其中某一個單獨的流程定義,整個釋出包都會被認為變更了,導緻整個釋出包下的所有流程定義都會被重新釋出,結果就是每個流程定義都生成了新版本,雖然其中隻有一個流程發生了改變

  • 為了定制釋出方式, 可以為SpringProcessEngineConfiguration指定一個額外的參數deploymentMode. 這個參數指定了比對多個資源時的釋出處理方式. 預設下這個參數支援設定三個值:
    • default: 把所有資源放在一個單獨的釋出包中,對這個釋出包進行重複檢測.這是預設值,如果你沒有指定參數值,就會使用它
    • single-resource: 為每個單獨的資源建立一個釋出包,并對這些釋出包進行重複檢測.你可以單獨釋出每個流程定義,并在修改流程定義後隻建立一個新的流程定義版本
    • resource-parent-folder: 把放在同一個上級目錄下的資源釋出在一個單獨的釋出包中,并對釋出包進行重複檢測.當需要多資源時需要建立釋出包;但是需要根據共同的檔案夾來組合一些資源時,可以使用
  • 将deploymentMode參數配置為single-resource的情況:
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
  ...
  <property name="deploymentResources" value="classpath*:/activiti/*.bpmn" />
  <property name="deploymentMode" value="single-resource" />
</bean>           
  • 如果想使用上面三個值之外的參數值,你需要自定義處理釋出包的行為.可以建立一個SpringProcessEngineConfiguration的子類,重寫getAutoDeploymentStrategy(String deploymentMode)方法. 這個方法中處理了對應deploymentMode的釋出政策

單元測試

  • 當內建Spring時,使用标準的Activiti測試工具類是非常容易地對業務流程進行測試:
@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());

  }
}           
  • 對于這種方式,需要在Spring配置中定義一個org.activiti.engine.test.ActivitiRulebean
<bean id="activitiRule" class="org.activiti.engine.test.ActivitiRule">
  <property name="processEngine" ref="processEngine" />
</bean>           

基于注解的配置

除了基于XML的配置以外,還可以選擇基于注解的方式來配置Spring環境.這與使用XML的方法非常相似,除了要使用@Bean注解.而且配置是使用java編寫的, 可以直接用于Activiti-Spring的內建

  • @EnableActiviti: 會建立一個Spring環境,并對Activiti流程引擎進行配置
    • 預設的記憶體H2資料庫,啟用資料庫自動更新
    • 一個簡單的DataSourceTransactionManager
    • 一個預設的SpringJobExecutor
    • 自動掃描processes/ 目錄下的bpmn20.xml檔案
@Configuration
  @EnableActiviti
  public static class SimplestConfiguration {

  }           
  • 可以直接通過注入操作Activiti引擎:
@Autowired
  private ProcessEngine processEngine;

  @Autowired
  private RuntimeService runtimeService;

  @Autowired
  private TaskService taskService;

  @Autowired
  private HistoryService historyService;

  @Autowired
  private RepositoryService repositoryService;

  @Autowired
  private ManagementService managementService;

  @Autowired
  private FormService formService;           
  • 預設值都可以自定義:
    • 如果配置了DataSource,就會代替預設建立的資料庫配置
    • 事務管理器,ob執行器和其他元件都與之相同
@Configuration
  @EnableActiviti
  public static class Config {

    @Bean
    public DataSource dataSource() {
        BasicDataSource basicDataSource = new BasicDataSource();
        basicDataSource.setUsername("sa");
        basicDataSource.setUrl("jdbc:h2:mem:anotherDatabase");
        basicDataSource.setDefaultAutoCommit(false);
        basicDataSource.setDriverClassName(org.h2.Driver.class.getName());
        basicDataSource.setPassword("");
        return basicDataSource;
    }

  }           

其他資料庫會代替預設的

  • 注意AbstractActivitiConfigurer用法,它暴露了流程引擎的配置,可以用來對它的細節進行詳細的配置:
@Configuration
@EnableActiviti
@EnableTransactionManagement(proxyTargetClass = true)
class JPAConfiguration {

    @Bean
    public OpenJpaVendorAdapter openJpaVendorAdapter() {
        OpenJpaVendorAdapter openJpaVendorAdapter = new OpenJpaVendorAdapter();
        openJpaVendorAdapter.setDatabasePlatform(H2Dictionary.class.getName());
        return openJpaVendorAdapter;
    }

    @Bean
    public DataSource dataSource() {
        BasicDataSource basicDataSource = new BasicDataSource();
        basicDataSource.setUsername("sa");
        basicDataSource.setUrl("jdbc:h2:mem:activiti");
        basicDataSource.setDefaultAutoCommit(false);
        basicDataSource.setDriverClassName(org.h2.Driver.class.getName());
        basicDataSource.setPassword("");
        return basicDataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(
        OpenJpaVendorAdapter openJpaVendorAdapter, DataSource ds) {
        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setPersistenceXmlLocation("classpath:/org/activiti/spring/test/jpa/custom-persistence.xml");
        emf.setJpaVendorAdapter(openJpaVendorAdapter);
        emf.setDataSource(ds);
        return emf;
    }

    @Bean
    public PlatformTransactionManager jpaTransactionManager(
        EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }

    @Bean
    public AbstractActivitiConfigurer abstractActivitiConfigurer(
        final EntityManagerFactory emf,
        final PlatformTransactionManager transactionManager) {

        return new AbstractActivitiConfigurer() {

            @Override
            public void postProcessSpringProcessEngineConfiguration(SpringProcessEngineConfiguration engine) {
                engine.setTransactionManager(transactionManager);
                engine.setJpaEntityManagerFactory(emf);
                engine.setJpaHandleTransaction(false);
                engine.setJobExecutorActivate(false);
                engine.setJpaCloseEntityManager(false);
                engine.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
            }
        };
    }

    // A random bean
    @Bean
    public LoanRequestBean loanRequestBean() {
        return new LoanRequestBean();
    }
}           

JPA和Hibernate

  • 在Activiti引擎的serviceTask或listener中使用Hibernate 4.2.x JPA時,需要添加Spring ORM依賴,Hibernate 4.1.x及以下版本是不需要的:
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-orm</artifactId>
  <version>${org.springframework.version}</version>
</dependency>