前言
本文基于 Activiti 7.0.0.GA 版本的源码,研究 Activiti 是如何整合到 Spring Boot 中的。依据 Spring Boot 自动配置原理和自定义实现 Spring Boot Starter 等网络文章,快速找到 Activiti 的 starter 模块的 META-INF\spring.factories 文件,研究整合细节。
Spring Boot 版本
从 activiti-sprint-boot-starter 模块的 pom.xml 分析得知 Activiti 7.0.0.GA 基于 spring-boot-2.1.2.RELEASE 开发,这个 spring boot 版本于 2018年12月发布。

源码
按照 Spring Boot 2.0 系列自动配置约定,打开 META-INF\spring.factories 配置文件,

该文件内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.activiti.spring.boot.EndpointAutoConfiguration,\
org.activiti.spring.boot.ProcessEngineAutoConfiguration
文件中放入了两个自动配置类,分别是:
- org.activiti.spring.boot.EndpointAutoConfiguration
- org.activiti.spring.boot.ProcessEngineAutoConfiguration 。
接下来研究这两个类的实现细节。
EndpointAutoConfiguration
EndpointAutoConfiguration 自动配置类,官方给出的解释是:这个模块背后的想法是 Spring Security 可以根据需要与 org.activiti.engine.IdentityService 通信。
该配置类上只配置了 ProcessEngineEndpoint 这个 Bean。
java
package org.activiti.spring.boot;
import org.activiti.engine.ProcessEngine;
import org.activiti.spring.boot.actuate.endpoint.ProcessEngineEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* The idea behind this module is that Spring Security could
* talk to the org.activiti.engine.IdentityService
* as required.
*/
@Configuration
@ComponentScan("org.activiti")
public class EndpointAutoConfiguration {
@Bean
@ConditionalOnEnabledEndpoint
public ProcessEngineEndpoint processEngineEndpoint(ProcessEngine engine) {
return new ProcessEngineEndpoint(engine);
}
}
ProcessEngineEndpoint 类,注册一个 Boot Actuator 端点,该端点提供有关正在运行的流程实例的信息,并呈现已部署流程的 BPMN 图。
java
package org.activiti.spring.boot.actuate.endpoint;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.persistence.deploy.DefaultDeploymentCache;
import org.activiti.engine.impl.persistence.deploy.DeploymentCache;
import org.activiti.engine.impl.persistence.deploy.ProcessDefinitionCacheEntry;
import org.activiti.engine.repository.ProcessDefinition;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Registers a Boot Actuator endpoint that provides information on the
* running process instance and renders BPMN diagrams of the deployed processes.
* 翻译:注册一个 Boot Actuator 端点,该端点提供有关正在运行的流程实例的信息,并呈现已部署流程的 BPMN 图。
*/
@ConfigurationProperties(prefix = "endpoints.activiti")
@Endpoint(id = "activiti")
public class ProcessEngineEndpoint {
private final ProcessEngine processEngine;
public ProcessEngineEndpoint(ProcessEngine processEngine) {
this.processEngine = processEngine;
}
@ReadOperation
public Map<String, Object> invoke() {
Map<String, Object> metrics = new HashMap<String, Object>();
// Process definitions
// 统计 processDefinition 数量
metrics.put("processDefinitionCount", processEngine.getRepositoryService().createProcessDefinitionQuery().count());
// List of all process definitions
// 列出所有的 processDefinition ,包括同一个 processDefinition 的不同版本。
List<ProcessDefinition> processDefinitions = processEngine.getRepositoryService().createProcessDefinitionQuery().orderByProcessDefinitionKey().asc().list();
List<String> processDefinitionKeys = new ArrayList<String>();
for (ProcessDefinition processDefinition : processDefinitions) {
processDefinitionKeys.add(processDefinition.getKey() + " (v" + processDefinition.getVersion() + ")");
}
metrics.put("deployedProcessDefinitions", processDefinitionKeys);
// Process instances
// 统计 正在运行的流程实例数量
Map<String, Object> processInstanceCountMap = new HashMap<String, Object>();
metrics.put("runningProcessInstanceCount", processInstanceCountMap);
for (ProcessDefinition processDefinition : processDefinitions) {
processInstanceCountMap.put(processDefinition.getKey() + " (v" + processDefinition.getVersion() + ")",
processEngine.getRuntimeService().createProcessInstanceQuery().processDefinitionId(processDefinition.getId()).count());
}
// 统计 已完成的流程实例数量
Map<String, Object> completedProcessInstanceCountMap = new HashMap<String, Object>();
metrics.put("completedProcessInstanceCount", completedProcessInstanceCountMap);
for (ProcessDefinition processDefinition : processDefinitions) {
completedProcessInstanceCountMap.put(processDefinition.getKey() + " (v" + processDefinition.getVersion() + ")",
processEngine.getHistoryService().createHistoricProcessInstanceQuery().finished().processDefinitionId(processDefinition.getId()).count());
}
// Open tasks
// 统计 处于运行状态的task
metrics.put("openTaskCount",
processEngine.getTaskService().createTaskQuery().count());
// 统计 处于已完成状态的task
metrics.put("completedTaskCount",
processEngine.getHistoryService().createHistoricTaskInstanceQuery().finished().count());
// Tasks completed today
// 统计 今天(24小时)刚完成的task
metrics.put("completedTaskCountToday",
processEngine.getHistoryService().createHistoricTaskInstanceQuery().finished().taskCompletedAfter(
new Date(System.currentTimeMillis() - secondsForDays(1))).count());
// Process steps
// 统计 处于已完成状态的 Activities
metrics.put("completedActivities",
processEngine.getHistoryService().createHistoricActivityInstanceQuery().finished().count());
// Process definition cache
// 统计 缓存的ProcessDefinition
DeploymentCache<ProcessDefinitionCacheEntry> deploymentCache = ((ProcessEngineConfigurationImpl) processEngine.getProcessEngineConfiguration()).getProcessDefinitionCache();
if (deploymentCache instanceof DefaultDeploymentCache) {
metrics.put("cachedProcessDefinitionCount",
((DefaultDeploymentCache) deploymentCache).size());
}
return metrics;
}
private long secondsForDays(int days) {
int hour = 60 * 60 * 1000;
int day = 24 * hour;
return days * day;
}
}
ProcessEngineAutoConfiguration
ProcessEngineAutoConfiguration 类关系图如下:
ProcessEngineAutoConfiguration 源码如下:
java
package org.activiti.spring.boot;
// 省略 import
@Configuration
@AutoConfigureAfter({DataSourceAutoConfiguration.class, TaskExecutionAutoConfiguration.class})
@EnableConfigurationProperties(ActivitiProperties.class)
public class ProcessEngineAutoConfiguration extends AbstractProcessEngineAutoConfiguration {
private final UserGroupManager userGroupManager;
public ProcessEngineAutoConfiguration(UserGroupManager userGroupManager) {
this.userGroupManager = userGroupManager;
}
@Bean
@ConditionalOnMissingBean
public SpringProcessEngineConfiguration springProcessEngineConfiguration(
DataSource dataSource,
PlatformTransactionManager transactionManager,
SpringAsyncExecutor springAsyncExecutor,
ActivitiProperties activitiProperties,
ProcessDefinitionResourceFinder processDefinitionResourceFinder,
@Autowired(required = false) ProcessEngineConfigurationConfigurer processEngineConfigurationConfigurer,
@Autowired(required = false) List<ProcessEngineConfigurator> processEngineConfigurators) throws IOException {
SpringProcessEngineConfiguration conf = new SpringProcessEngineConfiguration();
conf.setConfigurators(processEngineConfigurators);
configureProcessDefinitionResources(processDefinitionResourceFinder,
conf);
conf.setDataSource(dataSource);
conf.setTransactionManager(transactionManager);
if (springAsyncExecutor != null) {
conf.setAsyncExecutor(springAsyncExecutor);
}
conf.setDeploymentName(activitiProperties.getDeploymentName());
conf.setDatabaseSchema(activitiProperties.getDatabaseSchema());
conf.setDatabaseSchemaUpdate(activitiProperties.getDatabaseSchemaUpdate());
conf.setDbHistoryUsed(activitiProperties.isDbHistoryUsed());
conf.setAsyncExecutorActivate(activitiProperties.isAsyncExecutorActivate());
if (!activitiProperties.isAsyncExecutorActivate()) {
ValidatorSet springBootStarterValidatorSet = new ValidatorSet("activiti-spring-boot-starter");
springBootStarterValidatorSet.addValidator(new AsyncPropertyValidator());
if (conf.getProcessValidator() == null) {
ProcessValidatorImpl processValidator = new ProcessValidatorImpl();
processValidator.addValidatorSet(springBootStarterValidatorSet);
conf.setProcessValidator(processValidator);
} else {
conf.getProcessValidator().getValidatorSets().add(springBootStarterValidatorSet);
}
}
conf.setMailServerHost(activitiProperties.getMailServerHost());
conf.setMailServerPort(activitiProperties.getMailServerPort());
conf.setMailServerUsername(activitiProperties.getMailServerUserName());
conf.setMailServerPassword(activitiProperties.getMailServerPassword());
conf.setMailServerDefaultFrom(activitiProperties.getMailServerDefaultFrom());
conf.setMailServerUseSSL(activitiProperties.isMailServerUseSsl());
conf.setMailServerUseTLS(activitiProperties.isMailServerUseTls());
if (userGroupManager != null) {
conf.setUserGroupManager(userGroupManager);
}
conf.setHistoryLevel(activitiProperties.getHistoryLevel());
conf.setCopyVariablesToLocalForTasks(activitiProperties.isCopyVariablesToLocalForTasks());
conf.setSerializePOJOsInVariablesToJson(activitiProperties.isSerializePOJOsInVariablesToJson());
conf.setJavaClassFieldForJackson(activitiProperties.getJavaClassFieldForJackson());
if (activitiProperties.getCustomMybatisMappers() != null) {
conf.setCustomMybatisMappers(getCustomMybatisMapperClasses(activitiProperties.getCustomMybatisMappers()));
}
if (activitiProperties.getCustomMybatisXMLMappers() != null) {
conf.setCustomMybatisXMLMappers(new HashSet<>(activitiProperties.getCustomMybatisXMLMappers()));
}
if (activitiProperties.isUseStrongUuids()) {
conf.setIdGenerator(new StrongUuidGenerator());
}
if (activitiProperties.getDeploymentMode() != null) {
conf.setDeploymentMode(activitiProperties.getDeploymentMode());
}
conf.setActivityBehaviorFactory(new CloudActivityBehaviorFactory());
if (processEngineConfigurationConfigurer != null) {
processEngineConfigurationConfigurer.configure(conf);
}
return conf;
}
@Bean
@ConditionalOnMissingBean
public ProcessDefinitionResourceFinder processDefinitionResourceFinder(ActivitiProperties activitiProperties,
ResourcePatternResolver resourcePatternResolver) {
return new ProcessDefinitionResourceFinder(activitiProperties,
resourcePatternResolver);
}
@Bean
@ConditionalOnMissingBean
public ProcessDeployedEventProducer processDeployedEventProducer(RepositoryService repositoryService,
APIProcessDefinitionConverter converter,
@Autowired(required = false) List<ProcessRuntimeEventListener<ProcessDeployedEvent>> listeners) {
return new ProcessDeployedEventProducer(repositoryService,
converter,
Optional.ofNullable(listeners)
.orElse(Collections.emptyList()));
}
}
分析源码,梳理为以下三点:
- ProcessEngineAutoConfiguration 被定义在 DataSourceAutoConfiguration 和 TaskExecutionAutoConfiguration 两个自动配置类启动后再启动。
- ProcessEngineAutoConfiguration 通过 @EnableConfigurationProperties 启用 ActivitiProperties 的各项属性(application.yml 上的属性映射到了 ActivitiProperties 的属性中)。
- 在 ProcessEngineAutoConfiguration 自动配置类内,注册了三个Bean,分别是:
- SpringProcessEngineConfiguration:是 Activiti 中核心配置类 ProcessEngineConfigurationImpl 的子类,初始化 ProcessEngine 不可缺少的类。
- ProcessDefinitionResourceFinder:用于查找 xxx.bpmn20.xml 流程定义资源文件
- ProcessDeployedEventProducer:流程部署事件生产者,从现有代码看,侧重于测试,其它地方没有引用。
AbstractProcessEngineAutoConfiguration
ProcessEngineAutoConfiguration 的父类 AbstractProcessEngineAutoConfiguration 中注册了最核心的 Bean:ProcessEngineFactoryBean。ProcessEngineFactoryBean 这个 Bean 实现了 FactoryBean 接口,使得 Spring 可以通过该接口创建 Activiti 的心脏: ProcessEngine 流程引擎。除 ProcessEngine 外,还注册了我们常与流程引擎打交道用到的Bean,分别是:RuntimeService、RepositoryService、TaskService、HistoryService、ManagementService。
java
package org.activiti.spring.boot;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ManagementService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.impl.persistence.entity.integration.IntegrationContextManager;
import org.activiti.engine.integration.IntegrationContextService;
import org.activiti.spring.ProcessEngineFactoryBean;
import org.activiti.spring.SpringAsyncExecutor;
import org.activiti.spring.SpringCallerRunsRejectedJobsHandler;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.activiti.spring.SpringRejectedJobsHandler;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
/**
* Provides sane definitions for the various beans required to be productive with Activiti in Spring.
*
*/
public abstract class AbstractProcessEngineAutoConfiguration
extends AbstractProcessEngineConfiguration {
@Bean
public SpringAsyncExecutor springAsyncExecutor(TaskExecutor applicationTaskExecutor) {
return new SpringAsyncExecutor(applicationTaskExecutor, springRejectedJobsHandler());
}
@Bean
public SpringRejectedJobsHandler springRejectedJobsHandler() {
return new SpringCallerRunsRejectedJobsHandler();
}
protected Set<Class<?>> getCustomMybatisMapperClasses(List<String> customMyBatisMappers) {
Set<Class<?>> mybatisMappers = new HashSet<>();
for (String customMybatisMapperClassName : customMyBatisMappers) {
try {
Class customMybatisClass = Class.forName(customMybatisMapperClassName);
mybatisMappers.add(customMybatisClass);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Class " + customMybatisMapperClassName + " has not been found.", e);
}
}
return mybatisMappers;
}
@Bean
public ProcessEngineFactoryBean processEngine(SpringProcessEngineConfiguration configuration) {
return super.springProcessEngineBean(configuration);
}
@Bean
@ConditionalOnMissingBean
@Override
public RuntimeService runtimeServiceBean(ProcessEngine processEngine) {
return super.runtimeServiceBean(processEngine);
}
@Bean
@ConditionalOnMissingBean
@Override
public RepositoryService repositoryServiceBean(ProcessEngine processEngine) {
return super.repositoryServiceBean(processEngine);
}
@Bean
@ConditionalOnMissingBean
@Override
public TaskService taskServiceBean(ProcessEngine processEngine) {
return super.taskServiceBean(processEngine);
}
@Bean
@ConditionalOnMissingBean
@Override
public HistoryService historyServiceBean(ProcessEngine processEngine) {
return super.historyServiceBean(processEngine);
}
@Bean
@ConditionalOnMissingBean
@Override
public ManagementService managementServiceBeanBean(ProcessEngine processEngine) {
return super.managementServiceBeanBean(processEngine);
}
@Bean
@ConditionalOnMissingBean
public TaskExecutor taskExecutor() {
return new SimpleAsyncTaskExecutor();
}
@Bean
@ConditionalOnMissingBean
@Override
public IntegrationContextManager integrationContextManagerBean(ProcessEngine processEngine) {
return super.integrationContextManagerBean(processEngine);
}
@Bean
@ConditionalOnMissingBean
@Override
public IntegrationContextService integrationContextServiceBean(ProcessEngine processEngine) {
return super.integrationContextServiceBean(processEngine);
}
}
application.yml 配置
通过 @ConfigurationProperties("spring.activiti") 注解,将 application.yml 配置文件上的前缀是 spring.activiti 的属性映射到了 ActivitiProperties 类上。在这些属性中,processDefinitionLocationPrefix 配置 流程定义文件 的资源路径前缀,processDefinitionLocationSuffixes 配置 流程定义文件 的文件后缀。
java
package org.activiti.spring.boot;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.activiti.engine.impl.history.HistoryLevel;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.support.ResourcePatternResolver;
@ConfigurationProperties("spring.activiti")
public class ActivitiProperties {
private boolean checkProcessDefinitions = true;
private boolean asyncExecutorActivate = false;
private String deploymentName = "SpringAutoDeployment";
private String mailServerHost = "localhost";
private int mailServerPort = 1025;
private String mailServerUserName;
private String mailServerPassword;
private String mailServerDefaultFrom;
private boolean mailServerUseSsl;
private boolean mailServerUseTls;
private String databaseSchemaUpdate = "true";
private String databaseSchema;
private boolean dbHistoryUsed = false;
private HistoryLevel historyLevel = HistoryLevel.NONE;
private String processDefinitionLocationPrefix = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "**/processes/";
private List<String> processDefinitionLocationSuffixes = Arrays.asList("**.bpmn20.xml", "**.bpmn");
private List<String> customMybatisMappers;
private List<String> customMybatisXMLMappers;
private boolean useStrongUuids = true;
private boolean copyVariablesToLocalForTasks = true;
private String deploymentMode = "default";
private boolean serializePOJOsInVariablesToJson = true;
private String javaClassFieldForJackson = JsonTypeInfo.Id.CLASS.getDefaultPropertyName();
// 省略 getter 和 setter
}
总结
Activiti 与 Spring Boot 整合,本质上就是把人为手动创建 ProcessEngine、RuntimeService、TaskService、RepositoryService、HistoryService 的行为,改为由 Spring 来帮忙创建。
得益于 Spring Boot 的 约定优于配置 、 高度模块化 、 强大的扩展性等特点,使得 Activiti 与 Spring Boot 的整合变得简单,只需下面几步即可完成:
- 引入 spring-boot-autoconfigure 依赖包;
- 属性Propertie类上加上 @ConfigurationProperties 注解;
- 定义自动配置类,并加上 @EnableConfigurationProperties 和 @Configuration 注解;
- 在jar包上添加 META-INF/spring.factories 配置文件,把自动配置类配上。