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。

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 的整合变得简单,只需下面几步即可完成:

  1. 引入 spring-boot-autoconfigure 依赖包;
  2. 属性Propertie类上加上 @ConfigurationProperties 注解;
  3. 定义自动配置类,并加上 @EnableConfigurationProperties 和 @Configuration 注解;
  4. 在jar包上添加 META-INF/spring.factories 配置文件,把自动配置类配上。
相关推荐
小陈不好吃1 小时前
Spring Boot配置文件加载顺序详解(含Nacos配置中心机制)
java·开发语言·后端·spring
ゞ 正在缓冲99%…2 小时前
leetcode1770.执行乘法运算的最大分数
java·数据结构·算法·动态规划
渡我白衣2 小时前
链接的迷雾:odr、弱符号与静态库的三国杀
android·java·开发语言·c++·人工智能·深度学习·神经网络
007php0072 小时前
大厂深度面试相关文章:深入探讨底层原理与高性能优化
java·开发语言·git·python·面试·职场和发展·性能优化
qq_334466862 小时前
excel VBA应用
java·服务器·excel
E_ICEBLUE2 小时前
快速合并 Excel 工作表和文件:Java 实现
java·microsoft·excel
qq_214803292 小时前
ArcGIS Runtime与GeoTools融合实践:加密SHP文件的完整读写方案
java·安全·arcgis
Juchecar3 小时前
Spring是Java语境下的“最优解”的原因与启示
java·spring·node.js
邪恶喵喵3 小时前
Tomcat和负载均衡
java·tomcat·负载均衡