第 13 章 執行模式
這裡有三種基本的流程執行模式:對象,持久化和嵌入。 對於持久化和嵌入執行模式, 流程執行必 須在一個事務中執行。在那種情況, 流程執行必須放在一個環境的內部。 環境將用來綁定流程執行,更 新到一個應用事務的事務中。 環境可以被用來綁定,比如一個JDBC連接, JTA,BMT,Spring事務等等。
13.1. 對象執行模式
對象執行模式是使用流程虛擬機的最簡單形式。 這意味著通過客戶端API直接使用流程定義和執行對 象。 讓我們通過一個例子演示這個。 我們通過創建一個ClientProcessDefinition開始,看起來像這樣 :
對象執行模式是使用流程虛擬機的最簡單形式。 這意味著通過客戶端API直接使用流程定義和執行對 象。 讓我們通過一個例子演示這個。 我們通過創建一個ClientProcessDefinition開始,看起來像這樣 :
圖 13.1. 貸款流程
ClientProcessDefinition processDefinition = ProcessFactory.build("loan")
.activity("submit loan request").initial().behaviour(AutomaticActivity.class)
.transition().to("evaluate")
.activity("evaluate").behaviour(WaitState.class)
.transition("approve").to("wire money")
.transition("reject").to("end")
.activity("wire money").behaviour(AutomaticActivity.class)
.transition().to ("archive")
.activity("archive").behaviour(WaitState.class)
.transition ().to("end")
.activity("end").behaviour(WaitState.class)
.done();
ProcessFactory是一個幫助類, 為構建一個表現為流程定義的對象圖提供方便。 AutomaticActivity 是一個通過活動, 沒有任何操作發生,WaitState會等到外部signal發生。 這兩個活動實現都會在後面 討論更深。
processDefinition對象作為一個工廠,為流程實例對象。 一個流程實例表現為流程定義的一個執行 。 更准確的說,流程實例是執行的主路徑。
ClientExecution execution = processDefinition.startProcessInstance();
一個流程實例自己也是一個Execution. 潛在的,一個執行可以擁有子執行 表現執行的同步路徑。
execution可以看做是一個狀態機, 在流程定義裡像描述一樣操作。啟動一個流程實例意思是 流程定 義的初始節點被執行。 因為這是一個自動活動,執行會執行到evaluate活動。 evaluate活動是一個等待 狀態。 當執行到達evaluate活動,startProcessInstance方法 會返回並等待一個外部signal使用signal 方法提供。 所以在startProcessInstance之後,我們可以證實 如果執行定位在evaluate活動。
assertEquals("evaluate", execution.getActivityName());
為了讓流程執行得更遠,我們提供一個外部觸發器使用 signal方法。執行的結果會被作為 signalName參數給與,像這樣:
execution.signal("approve");
WaitState活動實現會根據給出的signalName 選擇轉移。所以執行將首先執行自動活動wire money 然 後在進入等待狀態archive後 返回。
assertEquals("archive", execution.getActivityName());
當執行在archive活動等待時,默認的signal會讓它 選擇第一個未命名的轉移。
execution.signal();
assertEquals("end", execution.getActivityName());
流程執行在客戶端的線程中。 startProcessInstance方法只在到達evaluate活動時返回。 換句話說 ,ClientProcessDefinition.startProcessInstance和 ClientExecution.signal方法會一直堵塞直到 下 一個等待狀態的到來。
13.2. 持久化執行模式
流程虛擬機也包含hibernate映射來保存流程定義和執行 在任何數據庫中。一個特定的會話外觀叫做 ExecutionService 被提供給流程執行 在這樣一個持久化環境中。
兩個配置文件應該放在classpath下:一個環境配置文件 和一個hibernate.properties文件。 一個持 久化執行模式的基礎配置,在一個標准Java環境 看起來像這樣:
environment.cfg.xml:
<jbpm-configuration>
<process-engine-context>
<deployer-manager>
<assign-file-type>
<file extension=".jpdl.xml" type="jpdl" />
</assign-file-type>
<parse-jpdl />
<check-process />
<check- problems />
<save />
</deployer-manager>
<process-service />
<execution-service />
<management -service />
<command-service>
<retry-interceptor />
<environment-interceptor />
<standard- transaction-interceptor />
</command-service>
<hibernate-configuration>
<properties resource="hibernate.properties" />
<mapping resource="jbpm.pvm.typedefs.hbm.xml" />
<mapping resource="jbpm.pvm.wire.hbm.xml" />
<mapping resource="jbpm.pvm.definition.hbm.xml" />
<mapping resource="jbpm.pvm.execution.hbm.xml" />
<mapping resource="jbpm.pvm.variable.hbm.xml" />
<mapping resource="jbpm.pvm.job.hbm.xml" />
<mapping resource="jbpm.jpdl.hbm.xml" />
<cache-configuration resource="jbpm.pvm.cache.xml"
usage="nonstrict-read-write" />
</hibernate-configuration>
<hibernate-session-factory />
<id-generator />
<types resource="jbpm.pvm.types.xml" />
<job-executor auto-start="false" />
</process-engine- context>
<transaction-context>
<hibernate-session />
<transaction />
<pvm-db-session />
<job-db-session />
<message-session />
</transaction- context>
</jbpm-configuration>
下一個,hibernate.properties像這樣:
hibernate.properties:
hibernate.dialect org.hibernate.dialect.HSQLDialect
hibernate.connection.driver_class org.hsqldb.jdbcDriver
hibernate.connection.url jdbc:hsqldb:mem:.
hibernate.connection.username sa
hibernate.connection.password
hibernate.hbm2ddl.auto create-drop
hibernate.cache.use_second_level_cache true
hibernate.cache.provider_class org.hibernate.cache.HashtableCacheProvider
# hibernate.show_sql true
hibernate.format_sql true
hibernate.use_sql_comments true
然後你可以從環境工廠中像這樣獲得服務:
EnvironmentFactory environmentFactory = new PvmEnvironmentFactory ("environment.cfg.xml");
ProcessService processService = environmentFactory.get (ProcessService.class);
ExecutionService executionService = environmentFactory.get (ExecutionService.class);
ManagementService managementService = environmentFactory.get (ManagementService.class);
ProcessService的責任是管理流程定義資源。 在我們可以啟動一個流程執行之前, 流程定義需要被 發布到流程資源庫中。 流程定義可以使用不同的格式和不同的流程語言提供。 一個發布包含了流程定義 信息,從不同的源文件中,像一個ZIP文件, 一個XML文件或一個流程定義對象。 ProcessService.deploy方法會獲得一個發布 通過配置在配置文件裡的所有發布器。
在這個例子中,我們通過代碼方式為發布 提供一個流程定義。
ClientProcessDefinition processDefinition = ProcessFactory.build("loan")
.activity("submit loan request").initial().behaviour(AutomaticActivity.class)
.transition().to("evaluate")
.activity("evaluate").behaviour(WaitState.class)
.transition("approve").to("wire money")
.transition("reject").to("end")
.activity("wire money").behaviour(AutomaticActivity.class)
.transition().to ("archive")
.activity("archive").behaviour(WaitState.class)
.transition ().to("end")
.activity("end").behaviour(WaitState.class)
.done();
Deployment deployment = new Deployment(processDefinition);
processService.deploy(deployment);
現在流程定義的一個版本保存到數據庫中。 check-version發布器會把版本1 分配給存儲的流程定義 。create-id發布器 會提取idloan:1 根據流程名稱和分配的版本。
再次發布流程會導致在數據庫中創建一個新流程定義。 但是一個增加的版本數會被分配。 出於版本 化的目的,如果有相同的名字, 流程定義就會相同。
推薦用戶為所有流程執行提供key的引用。 啟動一個新流程執行像這樣:
Execution execution = executionService.startExecution("loan:1", "request7836");
返回值是一個execution接口,防止關系的向導。 那是因為服務方法外面,事務和hibernate會話沒有 保證一直打開。 實際上,上面給出的默認的配置只保證 事務和會話在服務方法執行中是打開的。 所以 服務方法外的關系導航可能引起一個hibernate的 LazyInitializationException. 但是當前的活動名稱 還可以被驗證。
assertEquals("evaluate", execution.getActivityName());
生成可以被獲得的id也是非常重要的。 默認的id-generator會用來生成流程定義的id 給出的key來為 流程執行生成一個唯一id,像這樣:
assertEquals("loan:1/request7836", execution.getId());
那個id必須提供給外部觸發器 像這樣處理流程執行:
executionService.signalExecution("loan:1/request7836", "approve");
關於服務接口的更多信息,關於如何運行在持久化模式下, 可以在包 org.jbpm.pvm 的api doc找到 。
13.3. 嵌入執行模式
嵌入執行模式意味著路程的狀態保存在 一個用戶領域對象的字符串列中,比如一個loan.
public class Loan {
/** the loan process definition as a static resource */
private static final ClientProcessDefinition processDefinition = createLoanProcess ();
private static ClientProcessDefinition createLoanProcess() {
ClientProcessDefinition processDefinition = ProcessFactory.build("loan")
.activity("submit loan request").initial().behaviour(AutomaticActivity.class)
.transition().to("evaluate")
.activity("evaluate").behaviour (WaitState.class)
.transition("approve").to("wire money")
.transition("reject").to("end")
.activity("wire money").behaviour (AutomaticActivity.class)
.transition().to("archive")
.activity("archive").behaviour(WaitState.class)
.transition().to("end")
.activity("end").behaviour(WaitState.class)
.done();
return processDefinition;
}
/** exposes the process definition to the execution hibernate type */
private static ClientProcessDefinition getProcessDefinition() {
return processDefinition;
}
long dbid;
String customer;
double amount;
ClientExecution execution;
/** constructor for persistence */
protected Loan() {
}
public Loan (String customer, double amount) {
this.customer = customer;
this.amount = amount;
this.execution = processDefinition.startProcessInstance();
}
public void approve() {
execution.signal("approve");
}
public void reject() {
execution.signal("reject");
}
public void archiveComplete() {
execution.signal();
}
public String getState() {
return execution.getActivityName();
}
...getters...
}
如果你暫時忽略加粗部分,你可以看到這是一個沒有任何奇異的POJO. 它只是一個bean,可以保存到 hibernate中。 粗體部分展示了類的實現部分,這與流程和執行相關。 流程定義或者執行都沒有暴露給 Loan類的用戶。
每個Loan對象對應一個loan流程實例。 Loan類的一些方法 對應外部觸發器, 這會在Loan對象的生命 周期被觸發。
接下來我們演示如何使用這個類,為了開始,我們需要一個
hibernate.cfg.xml:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate- configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration- 3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">org.hibernate.dialect.HSQLDialect</property>
<property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="hibernate.connection.url">jdbc:hsqldb:mem:.</property>
<property name="hibernate.connection.username">sa</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.hbm2ddl.auto">create</property>
<property name="hibernate.show_sql">true"</property>
<property name="hibernate.format_sql">true"</property>
<property name="hibernate.use_sql_comments">true"</property>
<mapping resource="Loan.hbm.xml"/>
</session-factory>
</hibernate- configuration>
和一個
Loan.hbm.xml:
<?xml version="1.0"?<
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"<
<hibernate- mapping package="org.jbpm.pvm.api.db.embedded" default-access="field"<
<typedef name="execution" class="org.jbpm.pvm.internal.hibernate.ExecutionType" />
<class name="Loan" table="LOAN"<
<id name="dbid"<
<generator class="sequence"/>
</id<
<property name="execution" type="execution" />
<property name="customer" />
<property name="amount" />
</class<
</hibernate-mapping<
然後你可以在測試中像這樣使用Loan類
Configuration configuration = new Configuration();
configuration.configure ();
SessionFactory sessionFactory = configuration.buildSessionFactory();
// start a session/transaction
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Loan loan = new Loan("john doe", 234.0);
session.save(loan);
assertEquals("evaluate", loan.getState());
// start a new session/transaction
transaction.commit();
session.close();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
loan = (Loan) session.get(Loan.class, loan.getDbid());
assertEquals("evaluate", loan.getState ());
loan.approve();
assertEquals("archive", loan.getState());
// start a new session/transaction
transaction.commit();
session.close();
在執行這段代碼之後,這是在數據庫中的loan記錄:
圖 13.2. 數據庫中的貸款記錄