Activiti SpringBoot 自动部署分析

1,532 阅读3分钟

背景

SpringBoot集成Activiti的方式不讲,网上例子很多,简单说只要pom中引入对应的starter,在默认的路径下配置好流程(默认为classpath:/processes/),Spring 启动后就会将该路径下的所有流程文件自动部署一遍

<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-spring-boot-starter-basic</artifactId>
    <version>6.0.0</version>
</dependency>

但是自动部署有几个问题心里一直存疑: 每次启动都重新部署一遍么?如果有变更比对的话是根据什么比对的呢?如果只变更了一个流程其他的流程也会跟着重新部署么?

下面咱们一起看看他到底怎么搞得

分析

找到activiti-spring-boot-starter-basic-6.0.0.jar 这个包

熟悉SpringBoot的同学应该了解 这个文件里配置的就是这个starter要自动配置的bean 我们就从这里入手

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.activiti.spring.boot.DataSourceProcessEngineAutoConfiguration,\
    org.activiti.spring.boot.EndpointAutoConfiguration,\
    org.activiti.spring.boot.RestApiAutoConfiguration,\
    org.activiti.spring.boot.JpaProcessEngineAutoConfiguration,\
    org.activiti.spring.boot.SecurityAutoConfiguration

这里的类都会被spring加载到容器中,凭感觉DataSourceProcessEngineAutoConfiguration 应该是核心配置类

进到这个类里

@Configuration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class DataSourceProcessEngineAutoConfiguration {

  @Configuration
  @ConditionalOnMissingClass(name= "javax.persistence.EntityManagerFactory")
  @EnableConfigurationProperties(ActivitiProperties.class)
  public static class DataSourceProcessEngineConfiguration extends AbstractProcessEngineAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
      return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    @ConditionalOnMissingBean
    public SpringProcessEngineConfiguration springProcessEngineConfiguration(
            DataSource dataSource,
            PlatformTransactionManager transactionManager,
            SpringAsyncExecutor springAsyncExecutor) throws IOException {
      
      return this.baseSpringProcessEngineConfiguration(dataSource, transactionManager, springAsyncExecutor);
    }
  }
}

发现有两个配置的bean 一个是数据库事务的PlatformTransactionManager 一个是存配置信息的bean SpringProcessEngineConfiguration 这俩bean 应该都不是咱们要找的重点,之所以把这俩放外面 是因为这俩都是跟数据库相关的类 并不是通用,所以核心的通用的bean很可能是在那个AbstractXXX类里 那就继续到他的父类 AbstractProcessEngineAutoConfiguration 中去看看

AbstractProcessEngineAutoConfiguration 中的代码很多,都是一些bean的配置,溜一遍找到重点代码

  //引擎bean
  @Bean
  public ProcessEngineFactoryBean processEngine(SpringProcessEngineConfiguration configuration) throws Exception {
    return super.springProcessEngineBean(configuration);
  }

  @Bean
  @ConditionalOnMissingBean
  @Override
  public RuntimeService runtimeServiceBean(ProcessEngine processEngine) {
    return super.runtimeServiceBean(processEngine);
  }

凭直觉在引擎的创建里一定会搞事情,看命名就能猜到ProcessEngineFactoryBean 是一个spring的FactoryBean,是一个生产bean的bean,所以我们看他的 getObject 方法才是最终生成引擎的地方

public ProcessEngine getObject() throws Exception {
    configureExpressionManager();
    configureExternallyManagedTransactions();

    if (processEngineConfiguration.getBeans() == null) {
      processEngineConfiguration.setBeans(new SpringBeanFactoryProxyMap(applicationContext));
    }
	//在这里根据配置来实例化一个引擎
    this.processEngine = processEngineConfiguration.buildProcessEngine();
    return this.processEngine;
  }

只有一句重点 this.processEngine = processEngineConfiguration.buildProcessEngine();

这个 processEngineConfiguration 声明的时候是个抽象类 那他用的是哪个实现类呢?打断点看一下

断点看到是用了 SpringProcessEngineConfiguration 这个实现类 ,那就跟着断点进到这个方法里

@Override
  public ProcessEngine buildProcessEngine() {
    ProcessEngine processEngine = super.buildProcessEngine();
    ProcessEngines.setInitialized(true);
    //重点
    autoDeployResources(processEngine);
    return processEngine;
  }

终于我们看到了autoDeployResources这个方法 ,一看名字就知道找的就是他了 ,进去继续跟着断点走

  @Override
  public void deployResources(final String deploymentNameHint, final Resource[] resources, final RepositoryService repositoryService) {

    // Create a single deployment for all resources using the name hint as
    // the
    // literal name
    final DeploymentBuilder deploymentBuilder = repositoryService.createDeployment().enableDuplicateFiltering().name(deploymentNameHint);

    for (final Resource resource : resources) {
      final String resourceName = determineResourceName(resource);

      try {
        if (resourceName.endsWith(".bar") || resourceName.endsWith(".zip") || resourceName.endsWith(".jar")) {
          deploymentBuilder.addZipInputStream(new ZipInputStream(resource.getInputStream()));
        } else {
          deploymentBuilder.addInputStream(resourceName, resource.getInputStream());
        }
      } catch (IOException e) {
        throw new ActivitiException("couldn't auto deploy resource '" + resource + "': " + e.getMessage(), e);
      }
    }

    deploymentBuilder.deploy();

  }

发现它拿着资源居然直接开始部署了? 说好的历史比对呢?别放弃 继续跟 到 deploymentBuilder.deploy();里 一路跟下去

 public Deployment deploy(DeploymentBuilderImpl deploymentBuilder) {
    return commandExecutor.execute(new DeployCmd<Deployment>(deploymentBuilder));
  }

最终执行部署的是命令模式 DeployCmd 的执行 我们进去看看他具体怎么执行部署,只贴关键的代码

List<Deployment> existingDeployments = new ArrayList<Deployment>();
      if (deployment.getTenantId() == null || ProcessEngineConfiguration.NO_TENANT_ID.equals(deployment.getTenantId())) {
        DeploymentEntity existingDeployment = commandContext.getDeploymentEntityManager().findLatestDeploymentByName(deployment.getName());
        if (existingDeployment != null) {
          existingDeployments.add(existingDeployment);
        }
      } else {
        List<Deployment> deploymentList = commandContext.getProcessEngineConfiguration().getRepositoryService().createDeploymentQuery().deploymentName(deployment.getName())
            .deploymentTenantId(deployment.getTenantId()).orderByDeploymentId().desc().list();

        if (!deploymentList.isEmpty()) {
          existingDeployments.addAll(deploymentList);
        }
      }

      DeploymentEntity existingDeployment = null;
      
      if (!existingDeployments.isEmpty()) {
        existingDeployment = (DeploymentEntity) existingDeployments.get(0);
      }
		//重点 就是在这里比对的
      if ((existingDeployment != null) && !deploymentsDiffer(deployment, existingDeployment)) {
        return existingDeployment;
      }

简单分析一下 ,先把历史的部署信息 从 库里取出来 ,拿这次的部署信息 通过deploymentsDiffer 去比对一下是否需要新的部署

好 终于到了 我们要找的历史比对方法

protected boolean deploymentsDiffer(DeploymentEntity deployment, DeploymentEntity saved) {

    if (deployment.getResources() == null || saved.getResources() == null) {
      return true;
    }

    Map<String, ResourceEntity> resources = deployment.getResources();
    Map<String, ResourceEntity> savedResources = saved.getResources();

    for (String resourceName : resources.keySet()) {
      ResourceEntity savedResource = savedResources.get(resourceName);

      if (savedResource == null)
        return true;

      if (!savedResource.isGenerated()) {
        ResourceEntity resource = resources.get(resourceName);

        byte[] bytes = resource.getBytes();
        byte[] savedBytes = savedResource.getBytes();
        if (!Arrays.equals(bytes, savedBytes)) {
          return true;
        }
      }
    }
    return false;
  }

结论

我们看他比对了所有的流程配置文件,比对的是文件的内容和库里存的内容的是否完全一样,只要有一个文件发生了变动就会重新部署所有的流程