背景
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;
}
结论
我们看他比对了所有的流程配置文件,比对的是文件的内容和库里存的内容的是否完全一样,只要有一个文件发生了变动就会重新部署所有的流程