В данной статье я хочу рассмотреть пример создания простого приложения с использованием движка Activiti.
“Activiti” — это легковесная платформа (framework) для работы с бизнес-процессами (Business Process Managment), адаптированная для деловых людей, разработчиков и системных администраторов. Платформа основана на быстром и надёжном java-движке BPMN2-процессов. Проект OpenSource’ный и распространяется под лицензией Apache. Activiti может запускаться либо как часть вашего java-приложения, либо самостоятельно на сервере, кластере или облаке. Кроме того, она прекрасно интегрируется со Spring’ом.
По скольку проект обладает прекрасной документацией, я не буду описывать его основные концепции. Замечу только, что, как уже говорилось выше, мы можем использовать Activiti как самостоятельное приложение, а можем как движок/библиотеку для обработки бизнес-процессов, встраиваемый в приложение. Подробности можно посмотреть тут.
И так, начнём. Первым делом нам нужно создать проект с поддержкой Maven и описать в нём следующие зависимости:
Первые две зависимости (репозитарии для maven’a описаны тут) подключают ядро Activiti и набор утилит для интеграции со Spring’ом (подключение Spring’a не описано для экономии места). Вторая группа зависимостей подключает БД (в нашем случае будет использована in-memory) и пул для работы с ней.
База данных нужна Activiti для хранения информации о процессах, их состоянии, пользователях, истории и многом-многом другом.
Логично начать разработку приложения с описания бизнес-процесса. Прежде всего стоит ознакомиться с единицами описания бизнес-процессов, которые нам предлагает Activiti (точнее нотация BPMN 2.0). После этого можно приступать к описанию своего собственного бизнес-процесса. Сделать это можно тремя способами:
Не смотря на то, что самым логичным из перечисленного кажется третий пункт, мы всё же воспользуемся CASE-технологиями и нарисуем наш бизнес процесс с помощью плагина для Eclipse (важно выставить настройках плагина обновление xml-описания после каждого изменения диагрммы, иначе редактор начинает жить своей собственной жизнью и полностью игнорировать внешние раздражители в виде пользователя).
В результате у меня получилась следующая диаграмма:

ServiceTask “GenerateData” генерирует псевдослучайные данные (реализация с помощью класса GenerateDataService, имплементирующего интерфейс JavaDelegate)
в зависимости от которых выполняется либо ServiceTask “SayHelloA”, либо ServiceTask “SayHelloB”. Оба эти ServiceTask’a сводятся к вызову соответствующего метода службы SayHelloService (реализация на основе UEL-выражения "${sayHelloService.printMessageB(execution)"):
В XML-виде (то есть в формате BPMN 2.0), а именно он нужен движку Activiti для интерпретации, процесс выглядит следующим образом:
Теперь для, того что бы мы могли запустить процесс с помощью Activiti, необходимо настроить и инициализировать основные элементы ядра Activity. По скольку мы используем Spring, для нас настройка сведётся к конфигурации соответствующих bean’ов:
Важно отметить свойство deploymentResources bean’а processEngineConfiguration — оно указывает конфигуратору, где расположены файлы описания наших процессов. Каждый раз при старте приложения, необходимо “выложить” (deploy) эти процессы в движок Activiti. Подробнее deployment описан тут.
И так, основная работа уже сделана. Теперь для запуска приложения нам останется лишь создать контекст Spring’a и запустить нужный процесс:
Код описанного выше проекта доступен на GitHub.
“Activiti” — это легковесная платформа (framework) для работы с бизнес-процессами (Business Process Managment), адаптированная для деловых людей, разработчиков и системных администраторов. Платформа основана на быстром и надёжном java-движке BPMN2-процессов. Проект OpenSource’ный и распространяется под лицензией Apache. Activiti может запускаться либо как часть вашего java-приложения, либо самостоятельно на сервере, кластере или облаке. Кроме того, она прекрасно интегрируется со Spring’ом.
По скольку проект обладает прекрасной документацией, я не буду описывать его основные концепции. Замечу только, что, как уже говорилось выше, мы можем использовать Activiti как самостоятельное приложение, а можем как движок/библиотеку для обработки бизнес-процессов, встраиваемый в приложение. Подробности можно посмотреть тут.
Зависимости
И так, начнём. Первым делом нам нужно создать проект с поддержкой Maven и описать в нём следующие зависимости:
<!--
Activiti
-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>5.1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>5.1</version>
</dependency>
<!--
DataBase
-->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.2.132</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
* This source code was highlighted with Source Code Highlighter.
Первые две зависимости (репозитарии для maven’a описаны тут) подключают ядро Activiti и набор утилит для интеграции со Spring’ом (подключение Spring’a не описано для экономии места). Вторая группа зависимостей подключает БД (в нашем случае будет использована in-memory) и пул для работы с ней.
База данных нужна Activiti для хранения информации о процессах, их состоянии, пользователях, истории и многом-многом другом.
Бизнес-процесс
Логично начать разработку приложения с описания бизнес-процесса. Прежде всего стоит ознакомиться с единицами описания бизнес-процессов, которые нам предлагает Activiti (точнее нотация BPMN 2.0). После этого можно приступать к описанию своего собственного бизнес-процесса. Сделать это можно тремя способами:
- с помощью плагина к Eclipse (скудная палитра компонентов);
- с помощью специального приложения Activiti Modeler (нет возможности загрузки диаграмм, крайне странно ведёт себя сохранение);
- руками через XML-редактор.
Не смотря на то, что самым логичным из перечисленного кажется третий пункт, мы всё же воспользуемся CASE-технологиями и нарисуем наш бизнес процесс с помощью плагина для Eclipse (важно выставить настройках плагина обновление xml-описания после каждого изменения диагрммы, иначе редактор начинает жить своей собственной жизнью и полностью игнорировать внешние раздражители в виде пользователя).
В результате у меня получилась следующая диаграмма:

ServiceTask “GenerateData” генерирует псевдослучайные данные (реализация с помощью класса GenerateDataService, имплементирующего интерфейс JavaDelegate)
@Service
public class GenerateDataService implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
Long someData = Calendar.getInstance().getTimeInMillis() % 2;
execution.setVariable("someData", someData);
}
}
* This source code was highlighted with Source Code Highlighter.
в зависимости от которых выполняется либо ServiceTask “SayHelloA”, либо ServiceTask “SayHelloB”. Оба эти ServiceTask’a сводятся к вызову соответствующего метода службы SayHelloService (реализация на основе UEL-выражения "${sayHelloService.printMessageB(execution)"):
@Service
public class SayHelloService {
public void printMessageA(ActivityExecution execution) {
System.out.println("Hello world: variant A");
}
public void printMessageB(ActivityExecution execution) {
System.out.println("Hello world: variant B");
}
}
* This source code was highlighted with Source Code Highlighter.
В XML-виде (то есть в формате BPMN 2.0), а именно он нужен движку Activiti для интерпретации, процесс выглядит следующим образом:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
<process id="helloWorldProcess" name="Hello World">
<documentation>Simple "Hello World" process</documentation>
<startEvent id="startevent1" name="Start"></startEvent>
<serviceTask id="servicetask1" name="GenerateData" activiti:class="name.krestjaninoff.activiti.hello.process.GenerateDataService"></serviceTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1" targetRef="servicetask1"></sequenceFlow>
<serviceTask id="servicetask3" name="SayHelloB" activiti:expression="${sayHelloService.printMessageB(execution)}"></serviceTask>
<serviceTask id="servicetask4" name="SayHelloA" activiti:expression="${sayHelloService.printMessageA(execution)}"></serviceTask>
<exclusiveGateway id="exclusivegateway2" name="Exclusive Gateway"></exclusiveGateway>
<sequenceFlow id="flow4" name="" sourceRef="servicetask1" targetRef="exclusivegateway2"></sequenceFlow>
<sequenceFlow id="flow5" name="${someData != 0}" sourceRef="exclusivegateway2" targetRef="servicetask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${someData != 0}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow6" name="${someData == 0}" sourceRef="exclusivegateway2" targetRef="servicetask4">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${someData == 0}]]></conditionExpression>
</sequenceFlow>
<exclusiveGateway id="exclusivegateway3" name="Exclusive Gateway"></exclusiveGateway>
<sequenceFlow id="flow7" name="" sourceRef="servicetask3" targetRef="exclusivegateway3"></sequenceFlow>
<sequenceFlow id="flow8" name="" sourceRef="servicetask4" targetRef="exclusivegateway3"></sequenceFlow>
<sequenceFlow id="flow9" name="" sourceRef="exclusivegateway3" targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_helloWorldProcess">
<bpmndi:BPMNPlane bpmnElement="helloWorldProcess" id="BPMNPlane_helloWorldProcess">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="55" width="55" x="273" y="10"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="servicetask1" id="BPMNShape_servicetask1">
<omgdc:Bounds height="55" width="105" x="248" y="120"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="55" width="55" x="273" y="460"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="servicetask3" id="BPMNShape_servicetask3">
<omgdc:Bounds height="55" width="105" x="390" y="304"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="servicetask4" id="BPMNShape_servicetask4">
<omgdc:Bounds height="55" width="105" x="109" y="304"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="exclusivegateway2" id="BPMNShape_exclusivegateway2">
<omgdc:Bounds height="60" width="60" x="270" y="210"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="exclusivegateway3" id="BPMNShape_exclusivegateway3">
<omgdc:Bounds height="60" width="60" x="270" y="370"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="328" y="37"></omgdi:waypoint>
<omgdi:waypoint x="248" y="147"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
<omgdi:waypoint x="353" y="147"></omgdi:waypoint>
<omgdi:waypoint x="270" y="240"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
<omgdi:waypoint x="330" y="240"></omgdi:waypoint>
<omgdi:waypoint x="390" y="331"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
<omgdi:waypoint x="330" y="240"></omgdi:waypoint>
<omgdi:waypoint x="109" y="331"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
<omgdi:waypoint x="495" y="331"></omgdi:waypoint>
<omgdi:waypoint x="270" y="400"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
<omgdi:waypoint x="214" y="331"></omgdi:waypoint>
<omgdi:waypoint x="270" y="400"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow9" id="BPMNEdge_flow9">
<omgdi:waypoint x="330" y="400"></omgdi:waypoint>
<omgdi:waypoint x="273" y="487"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
* This source code was highlighted with Source Code Highlighter.
Настройки
Теперь для, того что бы мы могли запустить процесс с помощью Activiti, необходимо настроить и инициализировать основные элементы ядра Activity. По скольку мы используем Spring, для нас настройка сведётся к конфигурации соответствующих bean’ов:
<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 www.springframework.org/schema/beans/spring-beans.xsd
www.springframework.org/schema/context www.springframework.org/schema/context/spring-context-3.0.xsd
www.springframework.org/schema/tx www.springframework.org/schema/tx/spring-tx-3.0.xsd"
>
<!-- Configuration -->
<context:property-placeholder location="classpath*:*.properties" />
<!-- Annotation based configuration -->
<context:annotation-config />
<context:component-scan base-package="name.krestjaninoff" />
<!-- Data -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" 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>
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--
Activiti
-->
<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" />
<property name="deploymentResources" value="classpath*:/process/*.bpmn20.xml" />
</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>
* This source code was highlighted with Source Code Highlighter.
Важно отметить свойство deploymentResources bean’а processEngineConfiguration — оно указывает конфигуратору, где расположены файлы описания наших процессов. Каждый раз при старте приложения, необходимо “выложить” (deploy) эти процессы в движок Activiti. Подробнее deployment описан тут.
Запуск приложения
И так, основная работа уже сделана. Теперь для запуска приложения нам останется лишь создать контекст Spring’a и запустить нужный процесс:
public class Main {
public static void main(String[] args) {
// Create Spring context
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
// Start process
RuntimeService runtimeService = (RuntimeService) applicationContext.
getBean("runtimeService");
runtimeService.startProcessInstanceByKey("helloWorldProcess");
}
}
* This source code was highlighted with Source Code Highlighter.
Пример
Код описанного выше проекта доступен на GitHub.