
2.3 构造流程引擎实例对象
了解了流程配置文件的两种配置方式和流程引擎配置类的全局架构之后,接下来开始讲解如何构造流程引擎实例对象,首先使用ProcessEngines类创建ProcessEngine实例对象,具体实现如代码清单2-4所示,然后将上述两个流程配置文件放置到项目的classpath根路径中。
代码清单2-4 ProcessEngineTest.java

以上构造ProcessEngine实例对象的过程均是围绕ProcessEngines.getDefault-ProcessEngine()这行代码展开,getDefaultProcessEngine方法的相关实现,如代码清单2-5所示。
代码清单2-5 ProcessEngines.java

getDefaultProcessEngine方法仅仅是调用getProcessEngine方法进行下一步的处理,并传入getProcessEngine方法需要的参数值default(流程引擎的名称),接下来分析getProcessEngine方法的处理逻辑,该方法的相关实现如代码清单2-6所示。
代码清单2-6 ProcessEngines.java

getProcessEngine方法需要显式设置流程引擎的名称,因此开发者可以直接调用该方法构造流程引擎实例对象,该方法的执行逻辑如下。
(1)代码清单2-6中的第4行判断ProcessEngine实例对象是否已经被初始化,如果已经被初始化,则第7行直接根据processEngineName参数值从processEngines集合中查找ProcessEngine实例对象并作为该方法的返回值。通过该操作可以知道,Activiti支持多个ProcessEngine实例对象一起运行,只要流程引擎的名称不一样即可,相同名称的流程引擎只会存在一个流程引擎实例,因为processEngines为Map数据结构,流程引擎名称的配置可以参考代码清单2-1。流程引擎名称的默认值为default。
(2)如果ProcessEngine实例对象没有被初始化则调用init方法初始化ProcessEngine类,该方法的详细处理过程如代码清单2-7所示。
代码清单2-7 ProcessEngines.java

下面根据代码清单2-7概括init方法的处理逻辑。
(1)第3行再次确认ProcessEngine实例对象是否已经初始化,经过确认该实例对象没有被初始化,则开始执行后续操作,否则不予处理。
(2)如果processEngines集合为空,则执行第5行初始化该集合,该集合为Map数据结构,key为String类型,用于存储流程引擎的名称,value值为ProcessEngine实例对象,通过该集合的数据结构可知,如果流程引擎的名称相同则只会存储一份ProcessEngine实例对象。
(3)第10行定位activiti.cfg.xml文件的位置,然后构造ProcessEngine实例对象。文件定位的工作比较简单,首先获取类加载器classLoader对象,然后委托classLoader. getResources("activiti.cfg.xml")方法定位流程配置文件,通过该方法的处理逻辑可知该文件必须位于项目的根目录classpath中,第20行调用initProcessEnginFromResource方法构造ProcessEngine实例对象。
(4)第23行根据classLoader定位activiti-context.xml文件的位置,然后开始构造ProcessEngine实例对象。如果该文件存在,第29行直接委托initProcessEngineFrom-SpringResource方法构造ProcessEngine实例对象。
(5)以上操作执行完毕后第31行调用setInitialized(true)方法,如果该方法执行则表明构造ProcessEngine实例对象工作已经完毕。
上述为流程引擎初始化全过程概览,接下来细化讲解该过程中涉及的内容,首先讲解Activiti风格配置的流程引擎初始化过程。
2.3.1 初始化流程引擎之Activiti配置风格
initProcessEnginFromResource(resource)方法负责解析activiti.cfg.xml文件中配置的bean信息,该处理过程如代码清单2-8所示。
代码清单2-8 ProcessEngines.java

进入initProcessEnginFromResource方法之后,Activiti并不着急构造ProcessEngine实例对象,而是做了大量的准备工作,根据代码清单2-8,其处理流程总结如下。
(1)第2行根据resourceUrl参数值从processEngineInfosByResourceUrl集合中获取元素值,如果获取到,则第4行从processEngineInfos集合中移除该元素,第5行判断processEngineInfo对象是否存在异常信息,如果不存在异常信息,第7行从processEngines集合中移除该元素,紧接着第8行从processEngineInfosByName集合中移除该元素,最后不论processEngineInfo对象有无异常信息,都会执行第10行代码,从processEngineInfosByResourceUrl集合中移除该元素。
(2)第14行将构造ProcessEngine实例对象的工作交给buildProcessEngine方法完成。
(3)第15~23行开始向集合中添加元素,首先获取流程引擎的名称并实例化ProcessEngineInfoImpl类,然后分别对集合processEngineInfosByName、processEngine-InfosByResourceUrl、processEngineInfos执行添加操作。
2.3.2 构造流程引擎实例对象
上面提到了调用buildProcessEngine方法构造ProcessEngine实例对象,接下来详细分析该方法的处理过程,如代码清单2-9所示。
代码清单2-9 ProcessEngines.java

在buildProcessEngine(URLresource)方法中代码清单2-9中的第4行代码打开流程配置文件的数据流,第5、第6行开始创建processEngineConfiguration实例对象,第7行调用processEngineConfiguration对象的buildProcessEngine方法构造ProcessEngine实例对象,最后执行第11行代码关闭文件流,虽然很简单却回收了资源。接下来重点分析第5行和第6行代码的实现逻辑。
2.3.3 创建流程引擎配置类实例
ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(inputStream)方法完成流程引擎配置类ProcessEngineConfiguration的实例化工作,详细过程如代码清单2-10所示。
代码清单2-10 ProcessEngineConfiguration.java

上述代码中,第2行直接调用第4行定义的方法进行处理,并传入beanName参数值processEngineConfiguration,该参数值正是代码清单2-1中定义的bean的id值,第4行定义的方法处理逻辑也非常简单,直接委托BeansConfigurationHelper类中的parseProcessEngineConfigurationFromInputStream(inputStream, beanName)方法进行处理,该类的核心定义如代码清单2-11所示。
代码清单2-11 BeansConfigurationHelper.java

通过分析BeansConfigurationHelper类中定义的三个方法可以得出,Activiti底层通过该类巧妙地将流程配置文件中定义的bean全部交给Spring容器进行管理(包括类的配置、加载和获取操作),结合图2-3总结parseProcessEngineConfiguration方法的执行逻辑。

图2-3 Activiti类的配置、加载和获取操作
(1)第3行实例化DefaultListableBeanFactory类。
(2)第4行实例化XmlBeanDefinitionReader类,并将beanFactory设置到xmlBeanDefinitionReader对象中,该类非常重要,Spring框架使用XmlBeanDefinitionReader类读取和解析XML文件。
(3)第6行设置xmlBeanDefinitionReader对象的验证模式为XSD。
(4)第8行xmlBeanDefinitionReader.loadBeanDefinitions(springResource)方法加载配置文件activiti.cfg.xml中定义的所有bean。
(5)第10行使用beanFactory对象的getBean方法获取ProcessEngineConfigurationImpl实例对象,对应代码清单2-1配置的StandaloneProcessEngineConfiguration类,这就是最典型的Spring获取bean实例对象的操作方式。实例化StandaloneProcessEngineConfiguration类的同时会初始化其父类ProcessEngineConfigurationImpl中的各种属性值,如代码清单2-12所示。
代码清单2-12 ProcessEngineConfigurationImpl.java

以上这些服务类在实际项目开发中经常使用到,但通常是根据EngineServices实例对象获取以上这些服务类的实例对象,那么为什么这些服务类的实例化工作在ProcessEngineConfigurationImpl类中进行呢?暂且留下一个悬念,稍后讲解。
(6)实例化SpringBeanFactoryProxyMap类,该类的定义如代码清单2-13所示。
代码清单2-13 SpringBeanFactoryProxyMap.java

该类实现了Map接口,其内部持有BeanFactory实例对象,该实例对象主要是用于获取配置文件中定义的bean实例对象,所以只要开发人员能够获取到SpringBeanFactoryProxyMap实例对象,就可以获取到流程配置文件中定义的所有bean实例对象,Activiti这样设计很精巧,由衷感谢Activiti框架给开发者提供如此优秀的功能。
(7)将SpringBeanFactoryProxyMap实例对象设置到processEngineConfiguration对象中,因此只要能够获取processEngineConfiguration对象,就可以通过该对象的getBeans方法获取流程配置文件中定义的所有bean实例对象,不过由于这个地方Activiti使用的是Map数据结构,因此使用时需要进行类型转换。
2.3.4 初始化流程引擎
代码清单2-9中,buildProcessEngine()方法用于创建ProcessEngine实例对象,因为activiti.cfg.xml配置文件中定义的流程引擎配置类为StandaloneProcessEngineConfiguration,但是该类中并没有定义buildProcessEngine方法,那么buildProcessEngine方法肯定在其父类中进行了实现,接下来分析该方法在其父类中的处理逻辑,如代码清单2-14所示。
代码清单2-14 ProcessEngineConfigurationImpl.java

该方法的处理逻辑总结如下。
(1)调用init方法初始化各种属性值。
(2)实例化ProcessEngineImpl类。
2.3.5 初始化流程引擎之Spring配置风格
上述一系列的讲解均是针对Activiti配置文件的风格构造ProcessEngine实例对象,接下来详细分析Spring风格的配置文件activiti-context.xml(可以参考2.1.2节)构造ProcessEngine实例对象的整个过程,以下讲解均是围绕代码清单2-7中的initProcess-EngineFromSpringResource(resource)方法展开的,该方法的实现如代码清单2-15所示。
代码清单2-15 ProcessEngines.java

initProcessEngineFromSpringResource方法需要接收一个resource参数,其处理逻辑如下。
(1)第3行委托ReflectUtil类的静态方法loadClass加载SpringConfigurationHelper类(该类位于activiti-spring-5.21.0.jar包中)。关于类加载机制本书不详细讲解,其相关实现可跟进loadClass方法进行查看。
(2)第4行通过springConfigurationHelperClass对象查找SpringConfigurationHelper类中的buildProcessEngine方法。
(3)第5行通过反射方式调用SpringConfigurationHelper类中的buildProcessEngine方法,然后使用processEngine变量存储该方法的返回值。
(4)第6行获取流程引擎的名称。
(5)以上步骤执行完后,第7~9行将流程引擎的详细信息添加到processEngineInfosBy-Name和processEngineInfosByResourceUrl集合中。
2.3.5.1 反射构造ProcessEngine
上面代码通过反射方式调用SpringConfigurationHelper类中的buildProcessEngine方法,该方法的定义如代码清单2-16所示。
代码清单2-16 SpringConfigurationHelper.java

该方法的处理逻辑总结如下。
(1)第2行加载activiti-context.xml文件。
(2)第3行获取类型为ProcessEngine的实例对象,第4~5行如果activiti-context.xml文件中没有定义类型为ProcessEngine的bean则程序报错,第7行如果发现activiti-context.xml文件中配置了多个类型为ProcessEngine的bean,则从beansOfType集合中取出第一个ProcessEngine实例对象并作为方法的返回值,从beansOfType集合的处理逻辑可以看出流程配置文件中不管定义多少个ProcessEngine类,程序只会使用一个而且是第一个。接下来重点分析activiti-context.xml文件中定义的ProcessEngineFactoryBean类是如何被Spring实例化的,首先查看代码清单2-2中定义的ProcessEngineFactoryBean,该工厂类负责生成ProcessEngine实例对象,暂且将关注点放到ProcessEngineFactoryBean类的getObject方法的处理逻辑中,如代码清单2-17所示。
代码清单2-17 ProcessEngineFactoryBean.java

该方法的处理逻辑总结如下。
(1)第3行调用configureExpressionManager方法设置表达式管理器。
(2)第4行调用configureExternallyManagedTransactions方法设置事务管理器,具体实现如代码清单2-18所示。
代码清单2-18 ProcessEngineFactoryBean.java

如果processEngineConfiguration对象为SpringProcessEngineConfiguration实例对象,则进行如下处理,否则不予理会。
• 将processEngineConfiguration对象转换为SpringProcessEngineConfiguration实例对象。
• 如果engineConfiguration对象中的getTransactionManager方法返回值不为空,则将事务交给Spring框架管理,可以参考12.8节。通过这里的处理可以发现,如果使用Spring管理事务,则流程引擎配置类必须为SpringProcessEngineConfiguration。
(3)第9行开始构造流程引擎实例对象,processEngineConfiguration为SpringProcess-EngineConfiguration类型。buildProcessEngine方法的处理逻辑如代码清单2-19所示。
代码清单2-19 SpringProcessEngineConfiguration.java

buildProcessEngine方法的处理逻辑总结如下。
• 第2行委托父类构造ProcessEngine实例对象,因为当前类SpringProcessEngine-Configuration的父类为ProcessEngineConfigurationImpl,所以该操作会触发代码清单2-14中的逻辑。
• 第3行通知ProcessEngines类,ProcessEngine实例已经初始化。
• 第4行开始自动部署流程资源。
Activiti整合Spring框架时,提供了自动部署资源的特性,这样流程引擎启动时会自动将指定的资源文件部署到数据库中,具体的使用方式可以参照代码清单2-2中的配置,autoDeployResources(processEngine)方法的处理逻辑比较简单,本书不做过多解释。
注意
ProcessEngineFactoryBean类实现了FactoryBean接口。