06 Activiti 与 Spring Boot 整合
前言
本文基于 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。
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@ConditionalOnEnabledEndpointpublic ProcessEngineEndpoint processEngineEndpoint(ProcessEngine engine) {return new ProcessEngineEndpoint(engine);}
} ProcessEngineEndpoint 类,注册一个 Boot Actuator 端点,该端点提供有关正在运行的流程实例的信息,并呈现已部署流程的 BPMN 图。
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;}@ReadOperationpublic 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// 统计 处于运行状态的taskmetrics.put("openTaskCount",processEngine.getTaskService().createTaskQuery().count());// 统计 处于已完成状态的taskmetrics.put("completedTaskCount",processEngine.getHistoryService().createHistoricTaskInstanceQuery().finished().count());// Tasks completed today// 统计 今天(24小时)刚完成的taskmetrics.put("completedTaskCountToday",processEngine.getHistoryService().createHistoricTaskInstanceQuery().finished().taskCompletedAfter(new Date(System.currentTimeMillis() - secondsForDays(1))).count());// Process steps// 统计 处于已完成状态的 Activitiesmetrics.put("completedActivities",processEngine.getHistoryService().createHistoricActivityInstanceQuery().finished().count());// Process definition cache// 统计 缓存的ProcessDefinitionDeploymentCache<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 源码如下:
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@ConditionalOnMissingBeanpublic 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@ConditionalOnMissingBeanpublic ProcessDefinitionResourceFinder processDefinitionResourceFinder(ActivitiProperties activitiProperties,ResourcePatternResolver resourcePatternResolver) {return new ProcessDefinitionResourceFinder(activitiProperties,resourcePatternResolver);}@Bean@ConditionalOnMissingBeanpublic 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。
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 AbstractProcessEngineAutoConfigurationextends AbstractProcessEngineConfiguration {@Beanpublic 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;}@Beanpublic ProcessEngineFactoryBean processEngine(SpringProcessEngineConfiguration configuration) {return super.springProcessEngineBean(configuration);}@Bean@ConditionalOnMissingBean@Overridepublic RuntimeService runtimeServiceBean(ProcessEngine processEngine) {return super.runtimeServiceBean(processEngine);}@Bean@ConditionalOnMissingBean@Overridepublic RepositoryService repositoryServiceBean(ProcessEngine processEngine) {return super.repositoryServiceBean(processEngine);}@Bean@ConditionalOnMissingBean@Overridepublic TaskService taskServiceBean(ProcessEngine processEngine) {return super.taskServiceBean(processEngine);}@Bean@ConditionalOnMissingBean@Overridepublic HistoryService historyServiceBean(ProcessEngine processEngine) {return super.historyServiceBean(processEngine);}@Bean@ConditionalOnMissingBean@Overridepublic ManagementService managementServiceBeanBean(ProcessEngine processEngine) {return super.managementServiceBeanBean(processEngine);}@Bean@ConditionalOnMissingBeanpublic TaskExecutor taskExecutor() {return new SimpleAsyncTaskExecutor();}@Bean@ConditionalOnMissingBean@Overridepublic IntegrationContextManager integrationContextManagerBean(ProcessEngine processEngine) {return super.integrationContextManagerBean(processEngine);}@Bean@ConditionalOnMissingBean@Overridepublic IntegrationContextService integrationContextServiceBean(ProcessEngine processEngine) {return super.integrationContextServiceBean(processEngine);}
}
application.yml 配置
通过 @ConfigurationProperties("spring.activiti") 注解,将 application.yml 配置文件上的前缀是 spring.activiti 的属性映射到了 ActivitiProperties 类上。在这些属性中,processDefinitionLocationPrefix 配置 流程定义文件 的资源路径前缀,processDefinitionLocationSuffixes 配置 流程定义文件 的文件后缀。
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
}
总结
- 引入 spring-boot-autoconfigure 依赖包;
- 属性Propertie类上加上 @ConfigurationProperties 注解;
- 定义自动配置类,并加上 @EnableConfigurationProperties 和 @Configuration 注解;
- 在jar包上添加 META-INF/spring.factories 配置文件,把自动配置类配上。
