本文基于一個簡單的Demo流程介紹了Activiti框架啟動、部署、運行過程。
Demo準備
流程圖文件:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
<process id="hello" name="hello" isExecutable="true">
<!-- 流程開始節點 -->
<startEvent id="start" name="Start" ></startEvent>
<!-- serviceTask:執行me.likeyao.activiti.demo.HelloWorld的execute方法,打印hello world -->
<serviceTask id="helloworld" name="helloworld" activiti:class="me.likeyao.activiti.demo.HelloWorld"/>
<!-- 流程結束節點 -->
<endEvent id="end" name="End"></endEvent>
<!-- 流程遷移線:開始節點到serviceTask節點 -->
<sequenceFlow id="sid-1" sourceRef="start" targetRef="helloworld"></sequenceFlow>
<!-- 流程遷移線:serviceTask節點到結束節點 -->
<sequenceFlow id="sid-3" sourceRef="helloworld" targetRef="end"></sequenceFlow>
</process>
</definitions>
流程圖:
代碼:
public class App {
public static void main(String[] args) {
//創建流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//部署流程圖
processEngine.getRepositoryService().createDeployment().addClasspathResource("hello.bpmn20.xml").deploy();
//發起流程
processEngine.getRuntimeService().startProcessInstanceByKey("hello");
}
}
public class HelloWorld implements JavaDelegate{
public void execute(DelegateExecution execution) throws Exception {
System.out.println("Hello world!");
}
}
Demo實現的功能是發起一個流程,執行到流程的serviceTask節點時,打印Hello world!
,然后流程結束。
源碼版本:5.22.0
框架初始化
ProcessEngine
ProcessEngine是Activiti框架的門面,ProcessEngine本身不提供任何功能,通過getXXXService方法可以獲取到對應的Service對象執行操作。Demo中涉及到的兩個Service:
- RepositoryService:流程定義和流程部署相關功能。
- RuntimeService:流程實例相關功能(發起流程、獲取流程實例變量)。
ProcessEngineConfiguration
ProcessEngineConfiguration負責Activiti框架的屬性配置、初始化工作,初始化入口是buildProcessEngine方法,所有Activiti框架運行時需要用到的組件基本都在這里初始化:
public ProcessEngine buildProcessEngine() {
init();
return new ProcessEngineImpl(this);
}
protected void init() {
initConfigurators();
configuratorsBeforeInit();
initProcessDiagramGenerator();
initHistoryLevel();
initExpressionManager();
initDataSource();
initVariableTypes();
initBeans();
initFormEngines();
initFormTypes();
initScriptingEngines();
initClock();
initBusinessCalendarManager();
initCommandContextFactory();
initTransactionContextFactory();
initCommandExecutors();
initServices();
initIdGenerator();
initDeployers();
initJobHandlers();
initJobExecutor();
initAsyncExecutor();
initTransactionFactory();
initSqlSessionFactory();
initSessionFactories();
initJpa();
initDelegateInterceptor();
initEventHandlers();
initFailedJobCommandFactory();
initEventDispatcher();
initProcessValidator();
initDatabaseEventLogging();
configuratorsAfterInit();
}
這里有一個擴展點:ProcessEngineConfigurator。
public interface ProcessEngineConfigurator {
//組件初始化前
void beforeInit(ProcessEngineConfigurationImpl processEngineConfiguration);
//組件初始化后
void configure(ProcessEngineConfigurationImpl processEngineConfiguration);
//優先級
int getPriority();
}
在init初始化方法中,initConfigurators方法通過ServiceLoader加載ProcessEngineConfigurator。隨后在configuratorsBeforeInit和configuratorsAfterInit方法中分別調用ProcessEngineConfigurator的beforeInit和configure方法,使用戶可以在ProcessEngineConfiguration初始化前后編程式的修改屬性,替換Activiti默認組件。
流程部署
流程部署實現的功能是將xml格式的流程圖,轉化為Activiti框架運行時依賴的流程定義對象。
RepositoryService
Demo中通過以下代碼部署了一個流程:
processEngine.getRepositoryService().createDeployment().addClasspathResource("hello.bpmn20.xml").deploy();
createDeployment方法中創建了DeploymentBuilder對象,DeploymentBuilder對象負責讀取指定路徑的流程圖xml文件的內容(byte數組),并緩存在DeploymentEntity對象中:
public DeploymentBuilder addInputStream(String resourceName, InputStream inputStream) {
...
byte[] bytes = IoUtil.readInputStream(inputStream, resourceName);
ResourceEntity resource = new ResourceEntity();
resource.setName(resourceName);
resource.setBytes(bytes);
deployment.addResource(resource);
return this;
}
最終DeploymentBuilder的deploy方法會調用RepositoryService的deploy方法,完成流程部署:
public Deployment deploy() {
return repositoryService.deploy(this);
}
CommandExecutor
在RepositoryService的deploy方法中,使用了CommandExecutor對象:
public Deployment deploy(DeploymentBuilderImpl deploymentBuilder) {
return commandExecutor.execute(new DeployCmd<Deployment>(deploymentBuilder));
}
在Activiti中,大部分操作都以Command模式實現,例如部署流程圖的DeployCmd。CommandExecutor封裝了一系列的CommandInterceptor,在內部形成CommandInterceptor鏈,在命令執行前后做了攔截。Activiti框架提供了一些
CommandInterceptor實現:
名稱 | 作用 |
---|---|
CommandContextInterceptor | 用于生成命令執行的上下文(CommandContext)。 |
LogInterceptor | 開啟日志Debug級別后,打印日志。 |
JtaTransactionInterceptor | 開啟Jta事務 |
引入activiti-spring包,通過SpringTransactionInterceptor引入Spring的事務支持。
CommandExecutor在ProcessEngineConfigurationImpl的initCommandExecutors方法中初始化:
protected void initCommandExecutors() {
initDefaultCommandConfig();
initSchemaCommandConfig();
initCommandInvoker();
initCommandInterceptors();
initCommandExecutor();
}
可以設置ProcessEngineConfigurationImpl的customPreCommandInterceptors和customPostCommandInterceptors屬性,添加自定義的CommandInterceptor:
protected void initCommandInterceptors() {
if (commandInterceptors==null) {
commandInterceptors = new ArrayList<CommandInterceptor>();
if (customPreCommandInterceptors!=null) {
commandInterceptors.addAll(customPreCommandInterceptors);
}
commandInterceptors.addAll(getDefaultCommandInterceptors());
if (customPostCommandInterceptors!=null) {
commandInterceptors.addAll(customPostCommandInterceptors);
}
commandInterceptors.add(commandInvoker);
}
}
這里的pre和post是指Activiti框架getDefaultCommandInterceptors()的前后。
CommandInvoker是CommandInterceptor鏈的最后一個對象,負責調用Command:
public class CommandInvoker extends AbstractCommandInterceptor {
@Override
public <T> T execute(CommandConfig config, Command<T> command) {
return command.execute(Context.getCommandContext());
}
}
CommandContext
CommandContext是Activit框架Command執行的上下文,主要包含各種SessionFactory:
sessionFactories = processEngineConfiguration.getSessionFactories();
SessionFactory負責生成Session,Session是Activiti操作持久化對象的統一接口:
名稱 | 作用 |
---|---|
ProcessDefinitionEntityManager | 流程定義相關讀寫操作。 |
ExecutionEntityManager | 流程實例相關讀寫操作。 |
DefaultHistoryManager | 歷史記錄相關讀寫操作 |
CommandContext的生命周期
CommandConext在CommandContextInterceptor中創建,在finally代碼塊中銷毀:
public <T> T execute(CommandConfig config, Command<T> command) {
//首先嘗試從線程上下文的棧中獲取CommandContext
CommandContext context = Context.getCommandContext();
boolean contextReused = false;
//什么時候創建新的CommandContext?
//1、CommandConfig中指定了不復用CommandContext
//2、當前線程上下文中不存在CommandConext
//3、當前線程上下文中的CommandConext已經拋出異常
if (!config.isContextReusePossible() || context == null || context.getException() != null) {
context = commandContextFactory.createCommandContext(command); }
else {
contextReused = true;
}
try {
//將前面獲取到的CommandContext入棧
Context.setCommandContext(context);
Context.setProcessEngineConfiguration(processEngineConfiguration);
//執行下一個interceptor,在CommandInvoker中可以通過Context.getCommandContext()獲取線程上下文中的CommandContext
return next.execute(config, command);
} catch (Exception e) {
//記錄異常信息
context.exception(e);
} finally {
try {
//如果CommandContext不可復用,用完直接關閉
if (!contextReused) {
context.close();
}
} finally {
//出棧操作
Context.removeCommandContext();
Context.removeProcessEngineConfiguration();
Context.removeBpmnOverrideContext();
}
}
return null;
}
Activiti的框架可以在一個Command的執行過程中,調用另外一個Command,所以會出現是否需要復用CommandContext的選項,默認值為true。
流程的解析
在DeployCmd中,首先調用DeploymentEntityManager持久化存儲DeploymentEntity對象:
commandContext.getDeploymentEntityManager().insertDeployment(deployment);
然后調用DeploymentManager部署流程(流程解析):
commandContext.getProcessEngineConfiguration().getDeploymentManager().deploy(deployment, deploymentSettings);
DeploymentEntityManager
DeploymentEntityManager的deploy方法中循環調用Deployer對象的deploy方法,Activiti默認的Deployer是BpmnDeployer。
另外DeploymentEntityManager中還緩存了解析好的流程定義對象和Bpmn模型對象。
Activiti持久化的是流程圖xml文件,每次系統重新啟動都要執行一次“deploy”操作,生成ProcessDefinitionEntity對象。
BpmnDeployer
BpmnDeployer的deploy方法中包含幾個操作(代碼縮略版):
public void deploy(DeploymentEntity deployment, Map<String, Object> deploymentSettings) {
...
BpmnParse bpmnParse = bpmnParser.createParse().sourceInputStream(inputStream).setSourceSystemId(resourceName).deployment(deployment).name(resourceName);
bpmnParse.execute();
for (ProcessDefinitionEntity processDefinition: bpmnParse.getProcessDefinitions()) {
if (deployment.isNew()) {
ProcessDefinitionEntity latestProcessDefinition = ...
if (latestProcessDefinition != null) {
processDefinitionVersion = latestProcessDefinition.getVersion() + 1;
}else{
processDefinitionVersion = 1;
}
processDefinition.setId(idGenerator.getNextId());
dbSqlSession.insert(processDefinition);
}
...
}
}
- 通過BpmnParser對象創建BpmnParse。
- 調用BpmnParse的execute方法,將inputStream中的流程圖轉化為ProcessDefinitionEntity。
- 持久化ProcessDefinitionEntity對象。
BpmnParse
在BpmnParse的execute中完成了xml文件到ProcessDefinitionEntity對象的轉化:
public BpmnParse execute() {
//xml->bpmnModel
bpmnModel = converter.convertToBpmnModel(streamSource, validateSchema, enableSafeBpmnXml, encoding);
//bpmnModel-> ProcessDefinitionEntity
transformProcessDefinitions();
}
protected void transformProcessDefinitions() {
for (Process process : bpmnModel.getProcesses()) {
bpmnParserHandlers.parseElement(this, process);
}
}
在流程定義解析過程中,會涉及到兩套模型:
- Bpmn模型(由BpmnXMLConverter完成轉換)
- PVM模型(由BpmnParseHandlers完成轉換)
Bpmn模型
PVM模型
Bpmn模型更偏向于xml節點的描述,PVM模型是運行時模型。Bpmn模型中的ServiceTask、StartEvent等會統一映射轉換為PVM的ActivityImpl對象,ServiceTask和StartEvent等節點行為上的差別,體現在ActivityImpl對象持有的不同的ActivityBehavior上。
運行流程
創建流程實例
在demo中通過RuntimeService發起流程實例:
processEngine.getRuntimeService().startProcessInstanceByKey("hello");
在startProcessInstanceByKey方法中執行StartProcessInstanceCmd命令:
public class StartProcessInstanceCmd<T> implements Command<ProcessInstance>, Serializable {
...
public ProcessInstance execute(CommandContext commandContext) {
//獲取流程定義
ProcessDefinitionEntity processDefinition = ...
//創建流程實例
ExecutionEntity processInstance = processDefinition.createProcessInstance(businessKey);
//開始流程
processInstance.start();
return processInstance;
}
...
}
在StartProcessInstanceCmd方中通過流程定義ProcessDefinitionEntity創建了流程實例ExecutionEntity:
ExecutionEntity實現了一些重要接口:
- PVM相關的接口,賦予了ExecutionEntity流程驅動的能力,例如single、start方法。
- 實現VariableScope接口讓ExecutionEntity可以持久上下文變量。
- ProcessInstance接口暴露了ExecutionEntity關聯的ProcessDefinitionEntity的信息。
- PersistentObject接口代表ExecutionEntity對象是需要持久化。
在ExecutionEntity中維護類一個屬性:activity。activity屬性代表當前執行到哪個節點,在創建ExecutionEntity過程中會設置activity,使流程從某一個節點開始,默認是開始節點。
最后StartProcessInstanceCmd還調用ExecutionEntity的start方法開始驅動流程:
public void start() {
performOperation(AtomicOperation.PROCESS_START);
}
驅動流程
Activiti框架的流程運行于PVM模型之上,在流程運行時主要涉及到PVM中幾個對象:ActivityImpl、TransitionImpl和ActivityBehavior。
- ActivityImpl:ActivityImpl是流程節點的抽象,ActivityImpl維護流程圖中節點的連線,包括有哪些進線,有哪些出線。另外還包含節點同步/異步執行等信息。
- TransitionImpl:TransitionImpl包含source和target兩個屬性,連接了兩個流程節點。
- ActivityBehavior:每一個ActivityImpl對象都擁有一個ActivityBehavior對象,ActivityBehavior代表節點的行為。
ActivityImpl、TransitionImpl和ActivityBehavior只是描述了流程的節點、遷移線和節點行為,真正要讓ExecutionEntity流轉起來,還需要AtomicOperation的驅動:
AtomicOperation PROCESS_START = new AtomicOperationProcessStart();
AtomicOperation PROCESS_START_INITIAL = new AtomicOperationProcessStartInitial();
AtomicOperation PROCESS_END = new AtomicOperationProcessEnd();
AtomicOperation ACTIVITY_START = new AtomicOperationActivityStart();
AtomicOperation ACTIVITY_EXECUTE = new AtomicOperationActivityExecute();
AtomicOperation ACTIVITY_END = new AtomicOperationActivityEnd();
AtomicOperation TRANSITION_NOTIFY_LISTENER_END = new AtomicOperationTransitionNotifyListenerEnd();
AtomicOperation TRANSITION_DESTROY_SCOPE = new AtomicOperationTransitionDestroyScope();
AtomicOperation TRANSITION_NOTIFY_LISTENER_TAKE = new AtomicOperationTransitionNotifyListenerTake();
AtomicOperation TRANSITION_CREATE_SCOPE = new AtomicOperationTransitionCreateScope();
AtomicOperation TRANSITION_NOTIFY_LISTENER_START = new AtomicOperationTransitionNotifyListenerStart();
AtomicOperation DELETE_CASCADE = new AtomicOperationDeleteCascade();
AtomicOperation DELETE_CASCADE_FIRE_ACTIVITY_END = new AtomicOperationDeleteCascadeFireActivityEnd();
在ExecutionEntity的start方法中,調用了PROCESS_START,PROCESS_START做了幾件事:
- 獲取流程定義級別定義的監聽start事件的ExecutionListener,調用notify方法。
- 如果開啟了事件功能,發布ActivitiEntityWithVariablesEvent和ActivitiProcessStartedEvent。
- 調用PROCESS_START_INITIAL。
PROCESS_START_INITIAL也實現了類似的功能:
- 獲取初始節點上定義的監聽start事件的ExecutionListener,調用notify方法。
- 調用ACTIVITY_EXECUTE。
在Demo流程執行中涉及的AtomicOperation的鏈路主要包括:
- ACTIVITY_EXECUTE:調用當前activity的behavior。
- TRANSITION_NOTIFY_LISTENER_END:某個activity節點執行完畢,調用節點上聲明的監聽end事件的ExecutionListener。
- TRANSITION_NOTIFY_LISTENER_TAKE:觸發線上的ExecutionListener。
- TRANSITION_NOTIFY_LISTENER_START:某個activity節點即將開始執行,調用節點上的監聽start事件的ExecutionListener。
以Demo流程中的ServiceTask節點helloworld為例,在執行ACTIVITY_EXECUTE時,會獲取activity關聯的behavior:
public class AtomicOperationActivityExecute implements AtomicOperation {
public void execute(InterpretableExecution execution) {
...
ActivityImpl activity = (ActivityImpl) execution.getActivity();
ActivityBehavior activityBehavior = activity.getActivityBehavior();
activityBehavior.execute(execution);
...
}
}
ServiceTask解析時關聯的是ServiceTaskJavaDelegateActivityBehavior,execution方法:
public void execute(ActivityExecution execution) throws Exception {
//execution中調用了me.likeyao.activiti.demo.HelloWorld
execute((DelegateExecution) execution);
//離開當前節點
leave(execution);
}
在leave方法中調用了:
bpmnActivityBehavior.performDefaultOutgoingBehavior(execution);
performDefaultOutgoingBehavior方法會在當前activity的
出線中選擇一條,使流程流向下一個節點。在Demo中只有一條線存在:
protected void performOutgoingBehavior(ActivityExecution execution,
boolean checkConditions, boolean throwExceptionIfExecutionStuck, List<ActivityExecution> reusableExecutions) {
if (transitionsToTake.size() == 1) {
execution.take(transitionsToTake.get(0));
}
}
最終take方法會將流程驅動權交還到AtomicOperation中:
public class ExecutionEntity{
...
public void take(PvmTransition transition, boolean fireActivityCompletionEvent) {
...
setActivity((ActivityImpl)transition.getSource());
setTransition((TransitionImpl) transition);
performOperation(AtomicOperation.TRANSITION_NOTIFY_LISTENER_END);
...
}
...
}
AtomicOperation的問題
按照AtomicOperation的驅動模式,只有當遇到UserTask等需要等待single信號的節點,調用才會返回。這意味著當調用RuntimeService啟動一個流程實例時,要一直等到流程運行到一個UserTask節點調用才會返回,如果流程比較長耗時非常驗證。
另一個問題是當流程圖比較復雜,ExecutionListener數量比較多時,AtomicOperation之間的互相調用會導致調用棧非常深。
AtomicOperation驅動模式與ExecutionEntity、Behavior等綁定的比較緊密,暫時沒有特別好的辦法替換掉。
小結
本文主要介紹了Activiti框架的啟動、部署、運行的主鏈路,并沒有深入BPMN規范和Activit功能的具體實現,后續打算根據Activiti的用戶手冊,詳細分析每個功能的使用和實現。