成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

jBPM實現高級交互模式詳解

開發 后端
jBpm是一個非常好用的業務流程管理,其涉足領域包括業務流程管理、工作流、服務協作等。在這片文章中會針對jBpm高級交互模式的實現操作技巧進行一個詳細介紹,幫助大家理解。

JBPM是目前應用廣泛的Java工作流管理系統,在51CTO之前報道的J2EE工作流管理系統JBPM詳解中,我們曾詳細介紹過JBPM的工作原理和工作流應用方案。今天通過對JBPM中的四眼原則、任命和上報的實現來詳細講解如何使用JBPM實現高級交互模式。

#t#

許多通用業務流程都包含人類參與者。人類活動,從簡單場景(如人工批準)到復雜場景(涉及復雜的數據輸入),在流程實現中引入了新的方面,如人類交互模式。人類交互模式的一個典型集合包括:

1、四眼原則(The 4-eyes principle),通常又被稱為“職責分離”,它是決策由多人彼此獨立作出時的一個常見場景。在很多情況下,很容易就能得到另一個觀點/簽名。

2、任命(Nomination)是指上級根據團隊成員的任務安排、工作負荷或經驗人工地將任務分配給他的情形。

3、任務通常被建模來表達一種預期:它們將在確定時間段內完成。如果任務沒有按預期地進展,就需要一種上報(escalation)機制。兩種典型的上報實現是:重新分配任務,并常常附帶一個上報已經發生的通知;或任務未按時完成的通知(通常發給經理)。

4、鏈狀執行(Chained execution)是一個流程(片斷),其中的一系列步驟是由同一個人來完成。

在本文中,我將討論如何實現JBPM高級交互模式。

JBPM中的任務管理

JBPM的一個核心功能2是為人類管理任務和任務列表。JBPM允許將任務和任務節點作為整個流程設計的一部分使用。

任務一般在JBPM中定義成任務節點。單個任務節點可以包含一個或多個任務。包含任務節點的JBPM流程的一個公共行為就是等待任務節點中的全部任務完成,然后繼續執行。某個任務可被分配3 給個人、用戶組或泳道:

假如任務被分配給某個特定用戶,那么就只有這個使用者可以執行它。

假如任務被分配給某個用戶組,那么這個組內的任何參與者都能執行這個任務。JBPM使用的是參與者池(pooled actors)符號(它可以包含組名、組名列表和參與者個人列表等),而不是組ID。如果用戶開始執行在他們組任務列表中的任務,最終可能會引起沖突4——可能有多人開始執行相同的任務。為了避免這種情況,在開始執行任務之前,用戶應該將任務從組任務列表移動到他們自己的任務列表中。

泳道代表一個流程角色,它通常被分配給一個用戶組。它是一種指定流程中的多個任務要由同一參與者完成的機制5。因此,在第一個任務被分配給某個泳道之后,流程就會記住所有在相同泳道內的后續任務都將由同一參與者完成。

JBPM提供了兩種定義任務分配的基本方法:作為流程定義的一部分或通過編程實現。如果是作為流程定義的一部分,分配可以通過指定具體用戶、用戶組或泳道完成。此外,可以使用表達式根據流程變量動態確定某個具體用戶。完整的編程實現是基于分配處理器(assignment handler)的6,它允許任務根據任意的計算規則去查找用戶ID。那么如何才能很好的實現JBPM高級交互模式這一功能呢,讓我們繼續往下看。

流程定義描述流程實例的方式類似任務描述任務實例的方式。當流程執行時,一個流程實例——流程的運行時表示——就會被創建。類似,一個任務實例——任務的運行時表示——就會被創建。根據任務定義,任務實例被分配給一個參與者/參與者組。

任務實例的一個作用就是支持用戶交互——把數據顯示給用戶并從用戶那里收集數據。一個JBPM任務實例擁有訪問流程(令牌)變量7的全部權限,而且還可以有自己的變量。任務能夠擁有自己的變量對于以下場景非常有用:

在任務實例中創建流程變量的副本,這樣對任務實例變量的即時更新只有在該任務完成且這些副本被提交給流程變量時才會影響流程變量。

創建更好支持用戶活動的“派生(計算)”變量。

任務自己的變量在JBPM中是通過任務控制器處理器(task controller handler)支持的,它可以在任務實例創建時生成任務實例數據(從流程數據),并在任務實例完成時將任務實例數據提交給流程變量。

#p#

擴展Task類

JBPM高級交互模式的實現操作中任務的定義被包含在org.JBPM.taskmgmt.def.Task類中。為了支持四眼原則,我們需要給類增加以下的字段/方法(清單1):

  1. protected int numSignatures = 1;  
  2. public int getNumSignatures(){  
  3. return numSignatures;  
  4. }  
  5. public void setNumSignatures
    (int numSignatures){  
  6. this.numSignatures = 
    numSignatures;  

清單1 給Task類增加字段和方法

這個新的參數允許指定任務完成所需的JBPM高級交互模式任務處理人數量。缺省值為1,這意味著,只有1個用戶應該/可以處理這個任務。

JBPM使用Hibernate來向數據庫保存和讀取數據。為了讓我們新加的變量持久化,我們需要更新Task類的Hibernate配置文件(Task.hbm.xml),它在org.JBPM.taskmgmt.def文件夾中,增加代碼如下(清單2)

  1. <  property name="numSignatures" 
    column="NUMSIGNATURES_" /> 

清單2 在Task映射文件中指定新增域

為了讓我們新加的屬性能被流程定義和數據庫正確讀取,我們需要修改org.JBPM.jpdl.xml.JpdlXmlReader類以正確地讀取我們的新屬性(清單3)

  1. String numSignatureText = 
    taskElement.attributeValue
    ("numSignatures");  
  2. if (numSignatureText != null) {  
  3. try{  
  4. task.setNumSignatures
    (Integer.parseInt(num
    SignatureText));  
  5. }  
  6. catch(Exception e){}  

清單3 讀取numSignature屬性

最后,因為JpdlXmlReader根據模式來驗證XML,因此我們需要在jpdl-3.2.xsd中增加一個屬性定義(清單4):

  1. <  xs:element name="task"> 
  2. ………………….  
  3. <  xs:attribute name=
    "numSignatures" type=
    "xs:string" /> 

清單4 在jpdl-3.2.xsd中增加numSignatures屬性

當完成這些工作,JBPM高級交互模式的任務定義就被擴展可以使用numSignatures屬性(清單5):

  1. <  task name="task2" 
    numSignatures = "2">   
  2. <  assignment pooled-actors
    ="Peter, John"> 
  3. <  /assignment>   
  4. <  /task> 

清單5 給任務定義增加numSignatures屬性

#p#

擴展TaskInstance類

在JBPM高級交互模式實現操作到此處時,擴展完任務類后,我們還需要創建一個自定義的任務實例類來跟蹤分配給該任務實例的參與者,并確保所有被分配的參與者完成類執行(清單6)。

  1. package com.navteq.JBPM.extensions;  
  2. import java.util.Date;  
  3. import java.util.LinkedList;  
  4. import java.util.List;  
  5. import org.JBPM.JBPMException;  
  6. import org.JBPM.taskmgmt.exe.TaskInstance;  
  7. public class AssignableTaskInstance 
    extends TaskInstance {  
  8. private static final long 
    serialVersionUID = 1L;  
  9. private List< Assignee> assignees = 
    new LinkedList< Assignee>();  
  10. private String getAssigneeIDs(){  
  11. StringBuffer sb = new StringBuffer();  
  12. boolean first = true;  
  13. for(Assignee a : assignees){  
  14. if(!first)  
  15. sb.append(" ");  
  16. else   
  17. first = false;  
  18. sb.append(a.getUserID());  
  19. }  
  20. return sb.toString();  
  21. }  
  22. public List< Assignee> getAssignees() {  
  23. return assignees;  
  24. }  
  25. public void reserve(String userID) 
    throws 
    JBPMException{  
  26. if(task == null)  
  27. throw new JBPMException("can't 
    reserve instance with no task");  
  28. // Duplicate assignment is ok  
  29. for(Assignee a : assignees){  
  30. if(userID.equals(a.getUserID()))  
  31. return;  
  32. }  
  33. // Can we add one more guy?  
  34. if(task.getNumSignatures() > 
    assignees.size()){  
  35. assignees.add(new Assignee(userID));  
  36. return;  
  37. }  
  38. throw new JBPMException("task 
    is already reserved by " +  
  39. getAssigneeIDs());  
  40. }  
  41. public void unreserve(String userID){  
  42. for(Assignee a : assignees){  
  43. if(userID.equals(a.getUserID())){  
  44. assignees.remove(a);  
  45. return;  
  46. }  
  47. }  
  48. }  
  49. private void completeTask(Assignee 
    assignee, String transition){  
  50. assignee.setEndDate(new Date());  
  51. // Calculate completed assignments  
  52. int completed = 0;  
  53. for(Assignee a : assignees){  
  54. if(a.getEndDate() != null)  
  55. completed ++;  
  56. }  
  57. if(completed <  task.getNumSignatures())  
  58. return;  
  59. if(transition == null)  
  60. end();  
  61. else   
  62. end(transition);  
  63. }  
  64. public void complete(String userID, 
    String transition) throws 
    JBPMException{  
  65. if(task == null)  
  66. throw new JBPMException("can't 
    complete instance with no task");  
  67. // make sure it was reserved  
  68. for(Assignee a : assignees){  
  69. if(userID.equals(a.getUserID())){  
  70. completeTask(a, transition);  
  71. return;  
  72. }  
  73. }  
  74. throw new JBPMException("task 
    was not reserved by " + userID);  
  75. }  
  76. public boolean isCompleted(){  
  77. return (end != null);  
  78. }  

清單6 擴展TaskInstance類

這個JBPM高級交互模式的操作實現擴展了JBPM提供的TaskInstance類,并跟蹤完成該實例所需的參與者個數。它引入了幾個新方法,允許參與者預留(reserve)/退還(unreserve)任務實例,以及讓指定參與者完成任務執行。

#p#

JBPM高級交互模式的實現操作中,清單6的實現依賴一個支持類Assignee(清單7)

  1. package com.navteq.JBPM.extensions;  
  2. import java.io.Serializable;  
  3. import java.text.DateFormat;  
  4. import java.text.SimpleDateFormat;  
  5. import java.util.Date;  
  6. public class Assignee implements 
    Serializable{  
  7. private static final long 
    serialVersionUID = 1L;  
  8. private static final DateFormat 
    dateFormat = new   
  9. SimpleDateFormat("yyyy/MM/dd HH:mm:ss");  
  10. long id = 0;  
  11. protected String startDate = null;  
  12. protected String userID = null;  
  13. protected String endDate = null;  
  14. public Assignee(){}  
  15. public Assignee(String uID){  
  16. userID = uID;  
  17. startDate = dateFormat.format(new Date());  
  18. }  
  19. //////Setters and Getters //////  
  20. public long getId() {  
  21. return id;  
  22. }  
  23. public void setId(long id) {  
  24. this.id = id;  
  25. }  
  26. public String getStartDate() {  
  27. return startDate;  
  28. }  
  29. public void setStartDate(String startDate) {  
  30. this.startDate = startDate;  
  31. }  
  32. public String getUserID() {  
  33. return userID;  
  34. }  
  35. public void setUserID(String id) {  
  36. userID = id;  
  37. }  
  38. public String getEndDate() {  
  39. return endDate;  
  40. }  
  41. public void setEndDate(String endDate) {  
  42. this.endDate = endDate;  
  43. }  
  44. public void setEndDate(Date endDate) {  
  45. this.endDate = dateFormat.format(endDate);  
  46. }  
  47. public void setEndDate() {  
  48. this.endDate = dateFormat.format(new Date());  
  49. }  
  50. public String toString(){  
  51. StringBuffer bf = new StringBuffer();  
  52. bf.append(" Assigned to ");  
  53. bf.append(userID);  
  54. bf.append(" at ");  
  55. bf.append(startDate);  
  56. bf.append(" completed at ");  
  57. bf.append(endDate);  
  58. return bf.toString();  
  59. }  

清單7 Assignee類

#p#

JBPM高級交互模式自定義的TaskInstance類和Assignee類都必須保存到數據庫中。這意味著需要給這兩個類實現Hibernate映射14 (清單8,9):

  1. <  ?xml version="1.0"?>   
  2. <  !DOCTYPE hibernate-mapping PUBLIC "-
    //Hibernate/Hibernate Mapping DTD 3.0
    //EN" "http://hibernate.sourceforge.net
    /hibernate-mapping-3.0.dtd"
    >   
  3. <  hibernate-mapping auto-import="false" 
    default-access="field">   
  4. <  subclass namename="com.navteq.JBPM.
    extensions.AssignableTaskInstance"
     
    extends="org.JBPM.taskmgmt.exe.
    TaskInstance"
     discriminator-value="A">   
  5. <  list name="assignees" cascade="all" >   
  6. <  key column="TASKINSTANCE_" />   
  7. <  index column="TASKINSTANCEINDEX_"/>   
  8. <  one-to-many class="com.navteq.
    JBPM.extensions.Assignee"
     />   
  9. <  /list>   
  10. <  /subclass>   
  11. <  /hibernate-mapping> 

清單8 自定義任務實例的Hibernate映射文件

  1. <  ?xml version="1.0"?>   
  2. <  !DOCTYPE hibernate-mapping PUBLIC "-
    //Hibernate/Hibernate Mapping DTD 3.0
    //EN" "http://hibernate.sourceforge.net
    /hibernate-mapping-3.0.dtd"
    >   
  3. <  hibernate-mapping auto-import=
    "false" default-access="field">   
  4. <  class name="com.navteq.JBPM.
    extensions.Assignee"
     table=
    "JBPM_ASSIGNEE">   
  5. <  cache usage="nonstrict-read-write"/>   
  6. <  id name="id" column="ID_"> 
  7. <  generator class="native" /> 
  8. <  /id> <  !-- Content -->   
  9. <  property name="startDate" 
    column="STARTDATE_" />   
  10. <  property name="userID" 
    column="USERID_" />   
  11. <  property name="endDate" 
    column="ENDDATE_" />   
  12. <  /class>   
  13. <  /hibernate-mapping> 

清單9 Assignee類的Hibernate映射文件

要讓JBPM高級交互模式的實現操作能夠使用我們的自定義任務實例實現,我們還需要提供一個自定義的任務實例工廠(清單10)。

  1. package com.navteq.JBPM.extensions;  
  2. import org.JBPM.graph.exe.ExecutionContext;  
  3. import org.JBPM.taskmgmt.TaskInstanceFactory;  
  4. import org.JBPM.taskmgmt.exe.TaskInstance;  
  5. public class AssignableTaskInstanceFactory 
    implements TaskInstanceFactory {  
  6. private static final long serialVersionUID = 1L;  
  7. @Override  
  8. public TaskInstance createTaskInstance
    (ExecutionContext executionContext) {  
  9. return new AssignableTaskInstance();  
  10. }  

清單10 自定義的任務實例工廠

最后,為了讓JBPM運行時使用正確的任務實例工廠(清單10),還必須創建一個新的JBPM配置(清單11)。

  1. <  JBPM-configuration>   
  2. <  bean name="JBPM.task.instance.
    factory"
     class="com.navteq.JBPM.
    extensions.AssignableTask
    InstanceFactory"
     singleton="true" />   
  3. <  /JBPM-configuration> 

清單11 JBPM配置

完成所有這些變更之后(清單1-11),一個典型的任務處理顯示如下:

  1. List<  String> actorIds = 
    new LinkedList
    <  String>();  
  2. actorIds.add("Peter");  
  3. List<  TaskInstance> cTasks = 
    JBPMContext.getGroupTaskList(actorIds)  
  4. TaskInstance cTask = cTasks.get(0);  
  5. AssignableTaskInstance aTask = 
    (AssignableTaskInstance)cTask;  
  6. try{  
  7. aTask.reserve("Peter");  
  8. // Save  
  9. JBPMContext.close();  
  10. }  
  11. catch(Exception e){  
  12. System.out.println("Task " + 
    cTask.getName() + " is already reserved");  
  13. e.printStackTrace();  

清單12 處理可分配任務實例

這里,在得到某個用戶的任務實例并將其轉變成可分配任務實例之后,我們將試著預留它15。一旦預留成功,我們將關閉JBPM高級交互模式運行時以提交事務。

#p#

實現任命

JBoss JBPM可以非常輕易的實現手動將任務分配給特定用戶。根據JBPM提供的簡單API,可以完成將任務實例從一個任務列表移動到另一個任務列表,因此給某個用戶分配任務相當直接(清單13)

  1. List< String> actorIds = 
    new LinkedList< String>();  
  2. actorIds.add("admins");  
  3. String actorID = "admin";  
  4. List< TaskInstance> cTasks = 
    JBPMContext.getGroupTaskList(actorIds);  
  5. TaskInstance cTask = cTasks.get(0);  
  6. cTask.setPooledActors((Set)null);  
  7. cTask.setActorId(actorID); 

 

清單13 將任務重新分配給指定用戶

在JBPM高級交互模式的操作中,提供了2類不同的API來設置參與者池:一類接收字符串id數組,另一類則接收id集合。如果要清空一個池,就要使用那個接收集合的API(傳入一個null集合)。

實現上報

前面已經說過,上報一般被實現為任務的重新分配,并常常附帶一個上報已發生的通知;或是實現成一個任務未及時完成的通知。

實現為重新分配的上報

盡管JBPM不直接支持上報,但它提供了2個基本的機制:超時和重新分配(參見上節)。粗一看,實現上報只需將這二者結合即可,但是仔細一想還是存在一些困難:

JBPM實現中的關系并不總是雙向的。如,從一個任務節點我們可以找到所有這個節點定義的任務,但是從一個任務,并沒有API可以完成找到包含它的任務節點的工作16;由某個任務實例,你可以得到一個任務,但是沒有由某個任務得到所有實例的API,諸如此類。

超時不是發生在任務自身,而是發生在任務節點上。由于某個節點可以關聯多個任務,并且JBPM關系實現并不是雙向的(見上),因此要跟蹤當前任務實例就需要其他的支持手段。
以重新分配實現的上報的整個實現17涉及3個處理器:

負責給任務分配參與者的分配處理器。這個處理器跟蹤它是一個首次任務調用還是一個上報任務調用。清單14給出了一個分配處理器的例子。

  1. package com.sample.action;  
  2. import org.JBPM.graph.def.Node;  
  3. import org.JBPM.graph.exe.
    ExecutionContext;  
  4. import org.JBPM.taskmgmt.def.
    AssignmentHandler;  
  5. import org.JBPM.taskmgmt.exe.
    Assignable;  
  6. public class EscalationAssi
    gnmentHandler implements 
    AssignmentHandler {  
  7. private static final long 
    serialVersionUID = 1L;  
  8. @Override  
  9. public void assign(Assignable assignable, 
    ExecutionContext context)  
  10. throws Exception {  
  11. Node task = context.getToken().getNode();  
  12. if(task != null){  
  13. String tName = task.getName();  
  14. String vName = tName + "escLevel";  
  15. Long escLevel = (Long)context.
    getVariable(vName);  
  16. if(escLevel == null){  
  17. // First time through  
  18. assignable.setActorId("admin");  
  19. }  
  20. else{  
  21. // Escalate  
  22. assignable.setActorId("bob");  
  23. }  
  24. }  
  25. }  

清單14 分配處理器示例

在JBPM高級交互模式的實現操作中我們嘗試得到一個包含了給定任務上報次數的流程變量。如果變量未定義,則就分配“admin”為任務擁有者,否則任務就被分配給“bob”。在這個處理器中可以使用任何其他的分配策略。

任務實例創建動作處理器(清單15),它保存流程實例上下文的任務實例id

  1. package com.sample.action;  
  2. import org.JBPM.graph.def.ActionHandler;  
  3. import org.JBPM.graph.def.Node;  
  4. import org.JBPM.graph.exe.ExecutionContext;  
  5. import org.JBPM.taskmgmt.exe.TaskInstance;  
  6. public class TaskCreationActionHandler 
    implements ActionHandler {  
  7. private static final long 
    serialVersionUID = 1L;  
  8. @Override  
  9. public void execute(ExecutionContext 
    context) throws Exception {  
  10. Node task = context.getToken().getNode();  
  11. TaskInstance current = 
    context.getTaskInstance();  
  12. if((task == null) || (current == null))  
  13. return;  
  14. String tName = task.getName();  
  15. String iName = tName + "instance";  
  16. context.setVariable(iName, 
    new Long(current.getId()));  
  17. }  

清單15 任務實例創建處理器

JBPM高級交互模式中任務節點計時器觸發調用的超時處理器(清單16)。

  1. package com.sample.action;  
  2. import org.JBPM.graph.def.ActionHandler;  
  3. import org.JBPM.graph.def.GraphElement;  
  4. import org.JBPM.graph.exe.ExecutionContext;  
  5. import org.JBPM.taskmgmt.exe.TaskInstance;  
  6. public class EscalationActionHandler 
    implements ActionHandler {  
  7. private static final long 
    serialVersionUID = 1L;  
  8. private String escalation;  
  9. @Override  
  10. public void execute(ExecutionContext 
    context) throws Exception {  
  11. GraphElement task = context.getTimer().
    getGraphElement();  
  12. if(task == null)  
  13. return;  
  14. String tName = task.getName();  
  15. String vName = tName + "escLevel";  
  16. long escLevel = (long)context.
    getVariable(vName);  
  17. if(escLevel == null)  
  18. escLevel = new long(1);  
  19. else  
  20. escLevel += 1;  
  21. context.setVariable(vName, escLevel);  
  22. String iName = tName + "instance";  
  23. long taskInstanceId = (long)
    context.getVariable(iName);  
  24. TaskInstance current =   
  25. context.getJBPMContext().
    getTaskInstance(taskInstanceId);  
  26. if(current != null){  
  27. current.end(escalation);  
  28. }  
  29. }  

清單16 超時處理器

這個處理器首先記錄上報計數器,接著完成此節點關聯的任務實例。任務實例的完成伴隨有一個變遷(一般是回到任務節點)。

使用以上描述的處理器實現JBPM高級交互模式的上報的簡單流程例子顯示在清單17中。

  1. < ?xml version="1.0" encoding="UTF-8"?>   
  2. < process-definition xmlns="urn:JBPM.
    org:jpdl-3.2"
     name="escalationHumanTaskTest">   
  3. < start-state name="start">   
  4. < transition to="customTask"> 
  5. < /transition>   
  6. < /start-state>   
  7. < task-node name="customTask">   
  8. < task name="task2">   
  9. < assignment class="com.sample.action.
    EscalationAssignmentHandler"
    > 
  10. < /assignment>   
  11. < /task>   
  12. < event type="task-create">   
  13. < action name="Instance Tracking" 
    class="com.sample.action.
    TaskCreationActionHandler"
    > 
  14. < /action>   
  15. < /event>   
  16. < timer duedate="10 second" 
    name="Escalation timeout">   
  17. < action class="com.sample.action.
    EscalationActionHandler"
    > < escalation>   
  18. escalation   
  19. < /escalation>   
  20. < /action>   
  21. < /timer>   
  22. < transition to="end" name="to end"> 
  23. < /transition>   
  24. < transition to="customTask" 
    name="escalation"> 
  25. < /transition>   
  26. < /task-node>   
  27. < end-state name="end"> 
  28. < /end-state>   
  29. < /process-definition> 

清單17 簡單流程的上報

實現成通知的上報

JBPM為郵件傳遞提供了強大支持18,這使得實現成通知的上報變得極其簡單。郵件傳遞可由給節點附加定時器,然后觸發,它使用已經寫好的郵件動作來完成通知傳遞。

實現鏈狀執行

鏈狀執行直接由JBPM泳道支持,并不需要額外的開發。

總結

不管我們在自動化方面投入多少努力,面對復雜的業務流程,總免不了要有人工介入的可能。在這篇JBPM高級交互模式的操作介紹的文章中,我給出了一系列已建立的高級人工交互模式,并展示了用JBPM完成它是多么輕而易舉。

責任編輯:曹凱 來源: infoq.com
相關推薦

2009-08-25 18:04:30

C#實現Singlet

2009-06-26 13:51:49

jBPM4高級圖形執行

2011-06-28 15:18:45

Qt 單例模式

2010-06-04 15:59:45

Hadoop完全分布模

2009-06-26 09:15:31

jBPM4基本活動

2009-06-26 09:32:35

jBPM4基本活動

2010-10-19 16:32:46

MySQL

2021-09-12 07:30:10

配置

2010-02-06 13:42:36

C++單件模式

2009-06-24 16:23:29

jBPM 4.0配置

2010-05-12 16:13:04

2021-06-29 08:54:23

設計模式代理模式遠程代理

2019-08-30 07:24:16

2009-08-07 14:10:13

C# WebserviDelphi

2010-06-13 09:15:16

WinForm窗體

2011-06-28 15:01:01

Qt PIMPL

2021-07-07 10:31:19

對象池模式解釋器模式設計模式

2009-06-24 14:57:03

jBPM4架構

2009-06-25 17:13:51

jBPM與Spring

2009-06-11 13:53:35

jBPM用戶指南
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久视频一区 | 日韩在线中文 | 日韩在线综合网 | 7777精品伊人久久精品影视 | 欧美黄色片 | 日韩国产一区 | 成年人在线播放 | 日韩精品一区二区三区第95 | 国产精品夜夜春夜夜爽久久电影 | 精品一区在线 | 精品一区二区三区免费视频 | 日韩欧美在线免费观看视频 | 午夜影院在线视频 | 99精品久久久久久中文字幕 | 一级片免费在线观看 | 国产精品一区网站 | www.激情.com| 久草新在线 | 中文字幕一区在线观看视频 | 91福利在线观看视频 | 一级毛片免费看 | 日韩欧美国产精品 | 中文字幕在线播放第一页 | 日本成人中文字幕 | 91在线视频播放 | 日韩欧美在线视频 | 久久精品视频在线免费观看 | 日韩av在线一区 | 亚洲成a人片 | 特级毛片爽www免费版 | 久久久av中文字幕 | 亚洲一区二区中文字幕在线观看 | 亚洲欧美国产毛片在线 | 亚洲高清免费观看 | 超碰人人艹 | 九九综合 | 七七婷婷婷婷精品国产 | 日韩精品一区二区三区在线观看 | 中文字幕 亚洲一区 | 亚洲一区久久 | 国产剧情久久 |