Liferay7 BPM门户开发之5: Activiti和Spring集成
参考文档:
https://github.com/jbarrez/spring-boot-with-activiti-example
https://github.com/sxyx2008/spring-activiti-webapp
http://www.cnblogs.com/hongwz/p/5548473.html
Spring典型配置
<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" /> ...
自定义的实体类可以这么设置:
<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>
写注解:
@ContextConfiguration("classpath:org/activiti/spring/test/transaction/SpringTransactionIntegrationTest-context.xml")
然后就可以访问Activiti的工厂类和自定义的实体类,
比如RepositoryService的实例这样获得,同时这也是Spring集成环境下的bpmn文件的部署方式:
RepositoryService repositoryService = (RepositoryService) applicationContext.getBean("repositoryService"); String deploymentId = repositoryService .createDeployment() .addClasspathResource("org/activiti/spring/test/hello.bpmn20.xml") .deploy() .getId();
new 一个自定义的实体类实例:
UserBean userBean = (UserBean) applicationContext.getBean("userBean");
userBean.hello();
自定义的实体类:
public class UserBean { /** Spring 注入 */ private RuntimeService runtimeService; @Transactional public void hello() { runtimeService.startProcessInstanceByKey("helloProcess"); } public void setRuntimeService(RuntimeService runtimeService) { this.runtimeService = runtimeService; } }
表达式
在流程定义文件bpmn20.xml使用表达式
表达式可以灵活动态的使用类的方法,他可以实现流程计算逻辑和实体类的解耦,说具体些就是流程定义文件可以不用Hardcode了,直接通过接口调用的形式访问具体的类,
举例,定义一个输出打印的类,
public class Printer { public void printMessage() { System.out.println("hello world"); } }
在Spring里注册:
<beans> ... <bean id="printer" class="org.activiti.examples.spring.Printer" /> </beans>
建立关联配置:
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"> ... <property name="beans"> <map> <entry key="printer" value-ref="printer" /> </map> </property> </bean>
最后,就可以在流程定义文件bpmn20.xml使用表达式,定义了一个serviceTask:
这种调用方法非常灵活,也是集成Spring高效和方便的地方
<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>
使用Spring自动部署bpmn20.xml
仅当文件变化的时候,才会被重新部署,特别方便用于系统调试,程序员必备
<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>
可以使用通配符
<property name="deploymentResources" value="classpath*:/activiti/*.bpmn" /> <property name="deploymentMode" value="single-resource" />
Hibernate 4.2.x集成
POM:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.183</version> </dependency>
配置是不是很简单?
然后就可以开发了
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan @EnableAutoConfiguration public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } @Bean public CommandLineRunner init(final RepositoryService repositoryService, final RuntimeService runtimeService, final TaskService taskService) { return new CommandLineRunner() { @Override public void run(String... strings) throws Exception { System.out.println("Number of process definitions : " + repositoryService.createProcessDefinitionQuery().count()); System.out.println("Number of tasks : " + taskService.createTaskQuery().count()); runtimeService.startProcessInstanceByKey("oneTaskProcess"); System.out.println("Number of tasks after process start: " + taskService.createTaskQuery().count()); } }; } }
使用MySQL数据库
POM:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.34</version> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-jdbc</artifactId> <version>8.0.15</version> </dependency>
数据源
@Bean public DataSource database() { return DataSourceBuilder.create() .url("jdbc:mysql://127.0.0.1:3306/activiti-spring-boot?characterEncoding=UTF-8") .username("alfresco") .password("alfresco") .driverClassName("com.mysql.jdbc.Driver") .build(); }
REST开发扩展
Spring Boot可以快速开发web api接口,REST对集成第三方应用非常重要
POM:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring.boot.version}</version> </dependency>
开发服务类,服务类是用于控制器的重复调用,即帮助类
@Service public class MyService { @Autowired private RuntimeService runtimeService; @Autowired private TaskService taskService; @Transactional public void startProcess() { runtimeService.startProcessInstanceByKey("oneTaskProcess"); } @Transactional public List<Task> getTasks(String assignee) { return taskService.createTaskQuery().taskAssignee(assignee).list(); } }
控制器:
注意:启动流程使用POST,获取任务使用了GET,在输出时自动把实体类(TaskRepresentation)转换为JSON格式
@RestController public class MyRestController { @Autowired private MyService myService; @RequestMapping(value="/process", method= RequestMethod.POST) public void startProcessInstance() { myService.startProcess(); } @RequestMapping(value="/tasks", method= RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE) public List<TaskRepresentation> getTasks(@RequestParam String assignee) { List<Task> tasks = myService.getTasks(assignee); List<TaskRepresentation> dtos = new ArrayList<TaskRepresentation>(); for (Task task : tasks) { dtos.add(new TaskRepresentation(task.getId(), task.getName())); } return dtos; } static class TaskRepresentation { private String id; private String name; public TaskRepresentation(String id, String name) { this.id = id; this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } }
需要注意@ComponentScan一定在加在application类的注解
测试:
curl -X POST http://localhost:8080/process
curl http://localhost:8080/tasks?assignee=kermit
[{"id":"10004","name":"my task"}]
JPA支持
默认JPA是Hibernate
POM:
<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter-jpa</artifactId> <version>${activiti.version}</version> </dependency>
实体类:
@Entity class Person { @Id @GeneratedValue private Long id; private String username; private String firstName; private String lastName; private Date birthDate; public Person() { } public Person(String username, String firstName, String lastName, Date birthDate) { this.username = username; this.firstName = firstName; this.lastName = lastName; this.birthDate = birthDate; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Date getBirthDate() { return birthDate; } public void setBirthDate(Date birthDate) { this.birthDate = birthDate; } }
如果不使用in-memory数据库,数据表不会自动建立,需要 application.properties 增加配置:
spring.jpa.hibernate.ddl-auto=update
增加接口:
public interface PersonRepository extends JpaRepository<Person, Long> { Person findByUsername(String username); }
服务类:
@Service @Transactional public class MyService { @Autowired private RuntimeService runtimeService; @Autowired private TaskService taskService; @Autowired private PersonRepository personRepository; public void startProcess(String assignee) { Person person = personRepository.findByUsername(assignee); Map<String, Object> variables = new HashMap<String, Object>(); variables.put("person", person); runtimeService.startProcessInstanceByKey("oneTaskProcess", variables); } public List<Task> getTasks(String assignee) { return taskService.createTaskQuery().taskAssignee(assignee).list(); } public void createDemoUsers() { if (personRepository.findAll().size() == 0) { personRepository.save(new Person("jbarrez", "Joram", "Barrez", new Date())); personRepository.save(new Person("trademakers", "Tijs", "Rademakers", new Date())); } } }
在CommandLineRunner 初始化的时候,添加用户持久化数据(createDemoUsers())
@Bean public CommandLineRunner init(final MyService myService) { return new CommandLineRunner() { public void run(String... strings) throws Exception { myService.createDemoUsers(); } }; }
控制器:
@RestController public class MyRestController { @Autowired private MyService myService; @RequestMapping(value="/process", method= RequestMethod.POST) public void startProcessInstance(@RequestBody StartProcessRepresentation startProcessRepresentation) { myService.startProcess(startProcessRepresentation.getAssignee()); } ... static class StartProcessRepresentation { private String assignee; public String getAssignee() { return assignee; } public void setAssignee(String assignee) { this.assignee = assignee; } }
流程定义再一次使用了表达式:
<userTask id="theTask" name="my task" activiti:assignee="${person.id}"/>
测试:POST一个流程启动者的username
curl -H "Content-Type: application/json" -d '{"assignee" : "jbarrez"}' http://localhost:8080/process
curl http://localhost:8080/tasks?assignee=1
[{"id":"12505","name":"my task"}]
更高级的内容可以自行研究:
- Actuator support
- Spring Integration support
- Rest API integration
- Spring Security support
目前维护的开源产品:https://gitee.com/475660