【Activiti工作流】(三)Activiti之HelloWorld

HelloWorld已經成爲“入門小實例”的代名詞了,上節內容我們通過Activiti的官方實例演示了在某一業務流程中引入Activiti流程引擎帶來的便利,當然了,官方提供的app是讓我們體會其運行過程的,而我們自己要做的就是應用Activiti框架,構建適合我們自己的應用。我們本節內容就來學Activiti的入門實例。
一、Activiti框架結構
在這裏插入圖片描述
Activiti流程引擎的核心在於其提供的七大服務接口,大家可以通過從字面意思大概的瞭解下這七大服務組件的作用,這個我們今後會詳解。從圖中可以看出,這七大服務組件均由ProcessEngine創建,而ProcessEngine由其專門的配置類對象通過解析配置文件來創建 。所以,要想使用七大服務組件,首先需要創建ProcessEngine,要想創建ProcessEngine,首先要創建ProcessEngineConfiguration,要想創建ProcessEngineConfiguration,首先需要編寫activiti.cfg.xml配置文件。下面我們就開始我們的入門之旅。
二、Activiti入門實例
1、環境搭建
我們首先創建一個普通的maven項目,目錄結構如下:
在這裏插入圖片描述
1、1導入Activiti的核心包以及其依賴包
筆者採用maven座標的方式將依賴包導入項目,我們給出具體的依賴。主要的依賴包括spring、mysql、mybatis、log4j日誌包以及druid數據庫連接池。

<!-- 配置版本 -->
    <properties>
        <spring.version>4.3.17.RELEASE</spring.version>
        <mysql.version>5.1.47</mysql.version>
        <activiti.version>6.0.0</activiti.version>
        <mybatis.version>3.4.6</mybatis.version>
        <log4j.version>1.2.17</log4j.version>
        <druid.version>1.1.21</druid.version>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!-- activiti的依賴 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-engine</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- ssm集成的時候使用 -->
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring</artifactId>
            <version>${activiti.version}</version>
        </dependency>
        <!-- mysql驅動 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <!-- log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 配置編譯的jdk版本 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <!-- 指定source和target的版本 -->
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

1、2編寫log4j.properties日誌配置文件
在resources目錄下我們創建log4j.properties配置文件,文件內容我們直接給出:

log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n
log4j.logger.com.codahale.metrics=WARN
log4j.logger.com.ryantenney=WARN
log4j.logger.com.zaxxer=WARN
log4j.logger.org.apache=WARN
log4j.logger.org.hibernate=WARN
log4j.logger.org.hibernate.engine.internal=WARN
log4j.logger.org.hibernate.validator=WARN
log4j.logger.org.springframework=WARN
log4j.logger.org.springframework.web=WARN
log4j.logger.org.springframework.security=WARN

1、3創建mysql數據庫
在mysql中創建一個數據庫,名字可以隨便取,但是要記得與稍後要配置的activiti.cfg.xml配置文件中的數據庫名稱要一致,此處筆者取名activitistudy。至此,環境搭建完畢。下面開始正式進入Activiti的應用
2、流程引擎的創建
我們順着這樣的思路activiti.cfg.xml->ProcessEngineConfiguration->ProcessEngine->服務組件來進行開發。
2、1 activiti.cfg.xml文件的配置
在resources目錄下我們創建activiti.cfg.xml配置文件。我們先給出具體的配置然後再解釋

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <!--數據源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/activitistudy?useUnicode=true&amp;characterEncoding=utf8"/>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="username" value="root"/>
        <property name="password" value="ilovemcf"/>
    </bean>
    <!--事務管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--processEngineConfiguration配置-->
    <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
        <property name="dataSource" ref="dataSource"/>
        <property name="transactionManager" ref="transactionManager"/>
        <property name="databaseSchemaUpdate" value="true"></property>
    </bean>
</beans>

在以上配置文件中,我們配置了數據源,並指定了數據庫的連接屬性信息,其次我們配置了事務管理器,並將數據源bean注入到事務管理器中。最後配置了processEngineConfiguration,並將數據源屬性以及事務管理器注入到processEngineConfiguration中。此外仍有一個屬性需要引起大家的注意:

<property name="databaseSchemaUpdate" value="true"></property>

databaseSchemaUpdate直面意思意爲數據庫模式的更新策略,也就是當我們要創建一個流程引擎時,我們要對當前數據庫進行怎麼樣的一個操作,該屬性有四個枚舉值,此處瞭解即可
flase: 默認值。activiti在啓動時,會對比數據庫表中保存的版本,如果沒有表或者版本不匹配,將拋出異常。
true: activiti會對數據庫中所有表進行更新操作。如果表不存在,則自動創建。
create-drop: 在activiti啓動時創建表,在關閉時刪除表(必須手動關閉引擎,才能刪除表)。
drop-create: 在activiti啓動時刪除原來的舊錶,然後在創建新表(不需要手動關閉引擎)。
此處我們填寫true
2、2準備業務流程模型文件
還記得我們上節課在Activiti Modeler裏繪製的那個業務流程圖嗎,我們將其下載到本地,是一個名稱爲studentLeaveProcess.bpmn20.xml的文件,我們將該文件放到resources文件夾下。如果是用eclipse插件畫流程圖的同學,可以把.bpmn結尾的文件或是.bpmn文件以及圖片文件打包壓縮放到resources文件夾下。
2、3創建流程引擎
我們在Java源文件目錄下創建一個包以及測試類。通過調用如下代碼就可以創建Activiti的一個流程引擎:

 @Test
    public void createProcessEngine(){
        ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault();
        ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
    }

ProcessEngineConfiguration提供了多個重載的靜態方法來滿足通過不同的方式創建ProcessEngineConfiguration,createProcessEngineConfigurationFromResourceDefault()方法會自動加載resources文件夾下名稱爲activiti.cfg.xml的配置文件來創建ProcessEngineConfiguration。ProcessEngineConfiguration創建後,即可調用buildProcessEngine()方法創建流程引擎。流程引擎創建後,在控制檯上我們是看不出任何的變化的。我們將目光轉到我們自定義的mysql數據庫activitistudy中,刷新一下數據庫後你會驚訝的發現曾經空空如野的數據庫,現在突然冒出了28張表。除了act_ge_property表外,別的表只有結構沒有數據。其實這些表裏存儲的就是Activiti的某一流程在運行前、運行時、運行後的一些信息,因爲我們目前只創建了一個流程引擎,沒有任何流程在執行,肯定是空的表,而act_ge_property存儲的僅僅是一些版本信息。就好比你僅僅是造了一個汽車的發動機,沒把它安裝到任何一臺車上,車上的行車記錄儀,邁速表肯定是沒有信息的。
我們大概瞭解一下這些表的作用:

  • act_re_*: 'RE’表示repository。 這個前綴的表包含了流程定義和流程靜態資源(圖片,規則,等等)。
  • act_ru_*: 'RU’表示runtime。 這些運行時的表,包含流程實例,任務,變量,異步任務,等運行中的數據。 Activiti只在流程實例執行過程中保存這些數據,在流程結束時就會刪除這些記錄。
  • act_id_*: 'ID’表示identity。 這些表包含身份信息,比如用戶,組等等。
  • act_hi_*: 'HI’表示history。 這些表包含歷史數據,比如歷史流程實例,變量,任務等等。
  • act_ge_*: 通用數據,用於不同場景下,如存放資源文件

2、4創建服務組件

    @Test
    public void createService(){
        //另一種創建ProcessEngine的方式
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //創建流程存儲服務組件,實現對流程的部署
        RepositoryService repositoryService = processEngine.getRepositoryService();
        //創建運行時的服務組件
        RuntimeService runtimeService = processEngine.getRuntimeService();
        //創建任務服務組件
        TaskService taskService = processEngine.getTaskService();
    }

代碼中我們給出了另一種創建ProcessEngine的方式,ProcessEngine創建後,即可調用get*方法獲取對應的服務組件。
2、5使用RepositoryService 部署流程文件
一個業務流程/業務模式只有部署到Activiti中纔可以被Activiti驅動,就比如我們上節繪製完流程圖後需要將流程圖予App進行綁定纔可以被使用。而在當前項目中,我們僅僅是將流程圖文件studentLeaveProcess.bpmn20.xml放到了resources文件夾下,未做任何操作。Activiti的RepositoryService 服務組件就是用來完成流程文件的部署、刪除、掛機等操作。我們通過如下代碼來演示:

    @Test
    public void deployProcess(){
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //創建流程存儲服務組件,實現對流程的部署
        RepositoryService repositoryService = processEngine.getRepositoryService();
        Deployment deployment = repositoryService.createDeployment()
                                                 .addClasspathResource("studentLeaveProcess.bpmn20.xml")
                                                 .enableDuplicateFiltering()//過濾重複部署
                                                 .deploy();
        System.out.println(deployment.getId());
        System.out.println(deployment.getDeploymentTime());
    }

Activiti的接口允許我們以鏈式編程的方式進行調用,最終返回一個Deployment對象,該對象封裝了我們流程的部署信息,代碼中我們在控制檯輸出了部署完畢的id以及部署時間。同時我們發現act_re_deployment表中也有了信息,而且表中的信息與Deployment的屬性是對應的,說明Deployment與act_re_deployment存在一種映射關係。流程部署完畢後,那麼該業務流程的信息存放在了哪裏呢,答案是act_re_procdef(流程定義)表中,該表中存放了我們業務流程的定義信息。任何一條流程實例的啓動都是基於act_re_procdef表中的流程定義信息進行啓動。
流程的部署是系統管理員的職責。當流程部署完畢後,用戶就可以啓動該流程的一個實例,發起一次請假的審批。
2、6使用RuntimeService啓動一條流程實例

@Test
    public void startProcess(){
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        RuntimeService runtimeService = processEngine.getRuntimeService();
//        ProcessInstance processInstance = runtimeService.startProcessInstanceById("studentLeaveProcess:1:4");
        ProcessInstance processInstance1 = runtimeService.startProcessInstanceByKey("studentLeaveProcess");
        String id = processInstance1.getId();
        System.out.println(id);
    }

我們通過調用RuntimeService的多個重載方法中的其中一個就可以啓動一條流程實例,發起一次請假審批。本例中調用startProcessInstanceByKey,在代碼中註釋部分startProcessInstanceById同樣可以啓動一條流程實例。區別這兩種方式的不同前,我們首先看下act_re_procdef表
在這裏插入圖片描述
啓動某一條流程實例需要用到act_re_procdef表中的信息,ID_字段中流程定義的id在流程部署時可能產生變化,如果我們採用startProcessInstanceById方法啓動實例,需要到數據庫中查看流程定義id纔可以啓動。而KEY_字段與我們繪製流程圖時定義的Key一直,不會因爲對流程部署產生變化,所以筆者採用startProcessInstanceByKey啓動一條流程實例。當啓動一條流程實例後,就好比汽車發送機驅動汽車開始走了,行車記錄儀、邁速表、油量表等都開始運作了。Activiti的這28張表中的某些表相互配合便開始了對當前流程實例運行狀態的記錄了。我們來詳細看下這些表是如何記錄當前流程實例的運行過程的。
我們通過繪製的業務模型圖知道,流程實例啓動後,下一個執行的節點是【學生】請假申請,既然數據庫表中act_ru_*開頭的表都是記錄正在運行的流程實例的情況,我們就從act_ru_*表中的act_ru_execution開始看起,先看數據庫表的變化:從什麼都沒有變成了如下情況
在這裏插入圖片描述

【act_ru_execution】表中的信息用來表示當前流程實例的行進位置。爲什麼我們啓動了一條流程實例,而表中有兩條信息呢,我們着重第一條信息和第二條信息的異同
相同:PROC_INST_ID_、PROC_DEF_ID_、ROOT_PROC_INST_ID_、IS_ACTIVE_
相同的流程定義id與流程實例id表示這兩條信息都是對於同一條流程實例的狀態記錄。
不同:ID_,數據庫主鍵不同肯定是理所當然的,不同就有問題了;PARENT_ID_,第一條沒有,而第二條的PARENT_ID_(父id)爲第一條的ID_,說明第二條記錄是第一條的子執行流;ACT_ID_,第一條沒有,第二條爲studentLeaveApply,用來表示當前流程實例進行到了學生申請這個活動節點,studentLeaveApply爲我們定義的用戶任務這個活動的id。由此我們得出結論,第一條信息僅僅起到一個指示的作用,真正記錄當前流程實例進行位置的是第二條信息,也就是IS_SCOPE_=0這條信息,在流程實例的進行過程中,第一條信息,也就是IS_SCOPE_=1的信息不會變,變的只是第二條信息,也就是IS_SCOPE_=0這條信息,當流程實例結束後,這兩條信息,全部在當前表中消失。
【act_ru_task】主要用來存儲任務數據,比如當啓動流程實例後,下一個節點是用戶任務,那麼act_ru_task表中就存儲了當前的任務信息,來看數據庫
在這裏插入圖片描述
act_ru_task表中的EXECUTION_ID_、PROC_INST_ID_對應了當前的執行實例id與流程實例id,NAME_屬性表示當前的任務名稱,TASK_DEF_KEY_是我們當初繪製業務流程圖時定義的。ASSIGNEE_表示當前任務的辦理人。我們暫時就梳理這兩個表中的信息。其他表的信息,我們今後會有詳述。
2、6使用TaskService完成當前任務
我們先來看如何以最粗暴的方式完成當前任務:

@Test
    public void completeTask(){
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        taskService.complete("2505");
    }

通過taskService.complete(String taskId);直接傳入任務id號就可以完成當前任務,使業務流程轉向下一節點。但是這種直接從數據庫中取得任務id的方式實在有違天道人倫。而在開發中,我們一般都會查詢當前用戶的任務列表,然後獲取到任務id後再去完成當前任務。代碼如下:

 @Test
    public void completeTask(){
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        TaskService taskService = processEngine.getTaskService();
        List<Task> taskList = taskService.createTaskQuery()
                                         .taskCandidateOrAssigned("xialuo")//任務辦理人
                                         .list();//返回任務列表
        for (Task task : taskList) {
            taskService.complete(task.getId());
        }
    }

至此完成當前任務,業務流程走向【教師】初步審批節點,大家可以自行參上上述方式查看數據庫表的變化並自行完成後續任務的完成。入門案例我們講解到這裏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章