1、SSH整合JBPMhtml
JBPM基礎見http://www.cnblogs.com/kuangdaoyizhimei/p/4981551.html前端
如今將要實現SSH和JBPM的整合。java
1.添加jar包mysql
(1)jbpm項目/lib目錄下的全部jar包和根目錄下的jbpm.jar包放入/WEB-INF/lib文件夾下,同時刪除tomcat服務器/lib文件夾中的el-api.jar包。git
注意:必須刪除el-api.jar包,該jar包和jbpm項目中須要使用到的三個jar包衝突了:juel-api.jar、juel-engine.jar、juel-impl.jargithub
最好的解決方法就是將tomcat服務器根目錄下的/lib文件夾中的el-api.jar包使用上述的三個jar包替換掉,這樣也可以運行其餘的服務器程序。ajax
也就是說以上的三個jar包的功能可以代替el-api.jar包的功能,可是反之則不能夠。spring
(2)hibernate的相關jar包不須要添加了。jbpm項目中已經添加了hibernate相關的jar包。這裏推薦使用jbpm項目中提供的hibernate支持jar包。不然可能會出現一些莫名其妙的問題。sql
(3)mysql的驅動jar包也不須要添加了,由於該jar包也已經在jbpm項目中提供了。推薦使用jbpm項目中提供的mysql的驅動jar包。原生的更靠譜,可是親測使用Mysql官方網站提供的驅動jar包也沒有問題。數據庫
(4)添加spring的相關jar包。
(6)添加struts2的相關jar包。
(7)項目中會使用到ajax的相關功能,前端ajax和struts2交互使用struts2提供的jar包更方便(自定義結果集也可以實現,可是比較麻煩)
(7)推薦將使用到的jar包分門別類的放到一塊兒,方便管理。
ext文件夾存放了代替el-api.jar包的三個jar包;jstl文件夾用不着,我使用的是java EE 6.0的版本,自帶jstl的相關支持jar包;junit文件夾中存放單元測試的相關jar包,該jar包也不須要下載,myeclipse自帶,直接右鍵項目->Build Path->Add Libraries->JUnit也可以添加,注意選用4.0的版本。
2.配置文件的配置
建議將配置文件單獨放在一個source文件夾中,以下圖所示
SSH整合過程見
http://www.cnblogs.com/kuangdaoyizhimei/p/4855034.html
只說幾處和SSH和JBPM整合的關鍵地方:
(1)hibernate.cfg.xml配置文件被jbpm.hibernate.cfg.xml配置文件取代。兩個文件中的內容合併到一塊兒,最後在spring中聲明配置文件的位置便可(jpbm.hibernate.cfg.xml)
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:jbpm/jbpm.hibernate.cfg.xml"></property> </bean>
(2)jbpm相關的配置
jbpm最重要的一個對象就是ProcessEngine對象,全部的一切流程動做都須要今後對象出發,建立該對象的任務也須要交給spring,注意建立方式爲工廠方式。
<!-- 配置jbpm相關 --> <bean id="springHelper" class="org.jbpm.pvm.internal.processengine.SpringHelper"> <property name="jbpmCfg" value="jbpm/jbpm.cfg.xml"></property> </bean> <bean id="processEngine" factory-bean="springHelper" factory-method="createProcessEngine"></bean>
(3)其他配置過程就是SSH的整合過程了,不贅述。
3.SSH整合JBPM的過程主要仍是SSH整合的過程,因此SSH是基礎,必須熟練掌握。
2、項目演示
項目工程源代碼:https://github.com/kdyzm/sshAndJBPM
在項目運行以前,首先運行com.kdyzm.init.Init類,初始化用戶數據。
1.瀏覽器上輸入http://localhost:8080/jbpmAndSSH,出現登錄界面
輸入預置的用戶名張三,密碼xiaozhang(全部的用戶密碼統一爲xiaozhang)登錄
2.登錄後主界面
3.首先畫一張流程圖,打包成zip壓縮文件,單擊部署流程定義文檔進行部署。
單擊肯定按鈕以後:
查看流程圖:
至此,流程部署已經成功。
4.表單模板管理
該項功能能夠根據現有的流程定義新建流程模板等。
添加新模板:上傳一個模板文件
上傳成功以後:
5.發起申請
單擊第一項:請假流程表單模板
自動跳轉到「個人申請」查詢頁面
6.申請查詢
單擊查詢狀態,會顯示「我」的全部申請入上圖所示
單擊查看流轉記錄圖,能夠查看當前流程的審批進程
以上的流程圖是比較複雜的流程圖,以前的筆記中提到的fork/join節點在這裏使用到了。如今的流程實例須要項目組長和項目副組長共同贊成流程才能流向總經理審批節點。
紅色方框是怎麼動態顯示出來的是一個很是有意思的事情,使用CSS+DIV塊能夠精肯定位,對border設置樣式 便可。
7.審批
流程流向了項目組長審批(張三)和項目副組長(李四)審批的任務節點。當前帳戶是張三,由張三第一個審批。
單擊審批處理查看當前有權限處理的全部審批任務
單擊審批處理:
直接點擊贊成,返回到審批列表,這時任務已經結束,因此審批列表中已經沒有任務了。
8.再次查看個人申請,查看流程狀態
9.登錄李四(副組長)的帳戶,進行審批
和上面同樣如法炮製,點擊贊成,切換回張三的帳戶,查看審批進度,發現流程已經流轉到總經理審批的地方了
登錄王五(總經理)的帳戶,審批贊成,再次返回張三的帳戶查看審批進度
流程已經流轉到總裁審批的任務節點了。
接下來使用趙六(總裁)的帳戶登錄系統,進行審批,審批事後使用張三的帳戶登陸系統,再次查看流程狀態,這時已經查看不了流程圖了,由於申請狀態已經標誌爲「完成申請」
對比未完成的狀態,當前狀態發生了改變,同時不能查看流轉記錄圖了:
10.該小項目的主要功能就這麼多。
3、技術點分析
1.文件上傳
寫了一個文件上傳的工具類解決文件上傳的問題
package com.kdyzm.utils; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.UUID; import org.apache.commons.io.FileUtils; import org.apache.struts2.ServletActionContext; /** * 文件上傳的工具類 * @author kdyzm * */ public class FileUploadUtils { /** * 保存文件到指定的位置 * 注意保存的方法的問題 * @param sourceFile * @param infactFileName * @return */ public static File saveUploadFileToDestDir(File sourceFile, String infactFileName){ SimpleDateFormat sdf=new SimpleDateFormat("/yyyy/MM/dd"); Date date=new Date(); File dir=new File(ServletActionContext.getServletContext().getRealPath("/upload")+sdf.format(date)); if(!dir.exists()){ dir.mkdir(); } String []arrFileNames=infactFileName.split("\\."); String lastFileName=arrFileNames[arrFileNames.length-1]; File destFile=new File(dir,UUID.randomUUID().toString().replaceAll("-", "")+"."+lastFileName); try { FileUtils.copyFile(sourceFile, destFile); } catch (IOException e) { System.out.println("保存文件失敗!"); } return destFile; } }
文件上傳到了項目根目錄下的/upload/年/月/日文件夾下。
2.文件下載
須要注意的事項:
(1)使用URLEncoder類對文件名進行編碼,若是是英文名倒沒關係,若是是中文名,則必須使用該類
String fileName=URLEncoder.encode(formTemplate.getName(),"utf-8");
(2)設置響應頭信息,告訴瀏覽器返回的內容是須要下載的
response.setContentType("application/force-download");
response.setHeader("Content-Disposition","attachment;filename="+fileName);
(3)方法返回值爲NULL,在struts2中,若是返回值不是NULL,則會報異常,可是並不影響下載。
return null;
(4)下載表單模板完整事例
1 /** 2 * 下載指定formTemplateId指向的文件的方法 3 */ 4 public String downloadFormTemplateById() throws Exception{ 5 HttpServletRequest request=ServletActionContext.getRequest(); 6 HttpServletResponse response=ServletActionContext.getResponse(); 7 String formTemplateId=request.getParameter("formTemplateId"); 8 FormTemplate formTemplate=formTemplateService.getFormTemplateById(formTemplateId); 9 File file=new File(formTemplate.getUrl()); 10 String fileName=URLEncoder.encode(formTemplate.getName(),"utf-8"); 11 12 response.setContentType("application/force-download"); 13 response.setHeader("Content-Disposition","attachment;filename="+fileName); 14 FileInputStream fis=new FileInputStream(file); 15 OutputStream os=response.getOutputStream(); 16 int length=-1; 17 byte buff[]=new byte[1024]; 18 while((length=fis.read(buff))!=-1){ 19 os.write(buff, 0, length); 20 } 21 os.close(); 22 fis.close(); 23 return null; 24 }
3.如何發起申請
發起申請是整個流程中最爲複雜的一個部分。
分析發起申請的幾個步驟:
* 第一步:上傳申請表單
* 第二步:開啓流程實例
雖然只有兩步,可是每一步還有一些細節須要處理
(1)上傳申請表單
表單對象Form的屬性設置是很是重要的,它有以下幾個很是重要的屬性:
* FormTemplate :該表單使用了什麼表單模板
* Applicator :申請人是誰
* String state :當前的申請狀態
* String url :上傳以後表單存儲的位置
* String name :存儲的文件的名字和真正的名字是不相同的,使用該字段保存真正的名字
* String processId :綁定的流程實例ID,一個表單對象對應着一次申請流程,對應着一個流程實例;這裏不能綁定任務對象。由於任務對象會變,維護起來比較麻煩,並且對於fork/join形式的流程,沒法綁定。
以上的幾個屬性中,除了processId屬性以外,其他屬性在上傳申請表單的過程當中都要賦值。
1 //不只僅是上傳表單,還有更重要的流程控制的問題 2 public String uploadForm() throws Exception{ 3 Form form=new Form(); 4 HttpServletRequest request=ServletActionContext.getRequest(); 5 String formTemplateId=request.getParameter("formTemplateId"); 6 FormTemplate formTemplate=formTemplateService.getFormTemplateById(formTemplateId); 7 Date date=new Date(); 8 String dateString=DateUtils.dateToString(date); 9 User user=(User) request.getSession().getAttribute("user"); 10 //先部署流程實例 11 12 form.setApplicateTime(dateString); 13 form.setApplicator(user.getUserName()); //申請人設置,誰登錄系統的就是誰申請的 14 form.setDate(dateString); 15 form.setFormTemplate(formTemplate); 16 form.setState(FormState.SHENQINGZHONG); //一開始的時候申請狀態就是申請中 17 form.setTitle(formTemplate.getProcessKey()+"-"+user.getUserName()+"-"+dateString); 18 File file=FileUploadUtils.saveUploadFileToDestDir(formFile, formFileFileName); 19 form.setUrl(file.getAbsolutePath()); 20 form.setName(formFileFileName); 21 22 formService.saveForm(form); 23 jbpmProcessManagementService.startProcessByKey(formTemplate.getProcessKey(),form); 24 //發起申請以後應當跳轉到個人全部的申請表上 25 return "toListAllApplicationAction"; 26 }
(2)開啓流程實例
這裏開啓流程實例使用最高版本的流程定義來實現,使用pdkey來開啓流程實例。
另外還有一個很是重要的設計思想就是將form對象做爲流程變量放置到流程實例中。
同時,還須要將流程實例id屬性設置到form屬性processId中,並更新form表。
1 /** 2 * 完成開啓流程實例的關鍵的地方就是將form變量綁定到流程實例 3 */ 4 @Override 5 public void startProcessByKey(String processKey,Form form) { 6 //在開啓流程實例的時候將form對象綁定到流程實例上 7 Map<String,Object> variables=new HashMap<String,Object>(); 8 variables.put("form", form); 9 User user=(User) ServletActionContext.getRequest().getSession().getAttribute("user"); 10 variables.put("shenqingren", user.getUserName()); 11 ProcessInstance processInstance=processEngine.getExecutionService() 12 .startProcessInstanceByKey(processKey,variables); 13 14 //下一步完成任務,也就是說本身批准本身申請的那一步驟 15 Task task=this.processEngine 16 .getTaskService() 17 .createTaskQuery() 18 .executionId(processInstance.getId()) //根據流程實例id獲取爲一個的一個Task對象 19 .uniqueResult(); 20 21 form.setProcessId(processInstance.getId()); //將form對象和p流程實例對象綁定在一塊兒 22 formDao.updateEntry(form); 23 //完成任務 24 this.processEngine.getTaskService() 25 .completeTask(task.getId()); 26 }
(3)爲何要將form對象做爲流程變量放置到流程實例中?
這種方法是將Form對象和Task對象關聯到一塊兒的一種方法,在顯示全部審批列表的時候,急須要Form的信息,也須要Task的信息,可是Form表中並無關聯到Task對象,若是直接關聯到Task對象,維護Form表就變得至關麻煩了,因此採起了「迂迴戰術」解決這個問題:將form對象放到流程實例中這樣只要有了流程實例對象就能找到form對象;流程實例對象能夠根據Task對象來得到;Task對象能夠根據當前登陸人來得到(保存到了Session中),這樣繞了一圈好像挺費勁,可是實際上下降了Form對象和Task對象的耦合性,這是極佳的設計模式。
新建類TaskView,只有兩個屬性,一個是Task,一個是Form,這樣返回Task對象列表就能夠解決問題了。
1 //查詢當前全部任務列表的方法 2 @Override 3 public Collection<TaskView> getAllTaskViews() { 4 User user=(User) ServletActionContext.getRequest().getSession().getAttribute("user"); 5 //首先查找到當前用戶的的全部任務列表 6 List<Task> tasks=this.processEngine 7 .getTaskService() 8 .createTaskQuery() 9 .assignee(user.getUserName()) 10 .list(); 11 List<TaskView>taskViews=new ArrayList<TaskView>(); 12 //根據任務列表信息 13 for(Task task:tasks){ 14 TaskView taskView=new TaskView(); 15 Form form=(Form) this.processEngine.getExecutionService().getVariable(task.getExecutionId(), "form"); 16 taskView.setForm(form); 17 taskView.setTask(task); 18 taskViews.add(taskView); 19 } 20 return taskViews; 21 }
4.如何批准申請
所謂批准申請實際上就是完成任務的一個過程。因爲在這裏完成任務須要將流程變量繁殖到流程實例中,可是並不明確是不是最後一個任務,因此一概採用先把流程變量放置到流程實例中,再完成任務,防止出錯(在JBPM基礎中,曾經說過,若是完成任務的時候使用重載方法completeTask(String taskId,Map variables)的方法,若是當前完成的任務是流程實例中的最後一個任務,則會報錯,這是JBPM4.4中的一個BUG)。
這樣批准申請的過程就變成了:獲取流程變量->將流程變量放入流程實例->結束任務->改變Form對象狀態(只有當完成的任務是最後一個任務的時候才改變Form對象的狀態)。
在完成批准申請的時候有一個重要的東西須要特別注意:如何判斷當前任務是最後一個任務?
(1)對於單線程的流程來講,流程實例只有一個,直接使用
this.processEngine.getExecutionService().createProcessInstanceQuery().processInstanceId(task.getExecutionId()).uniqueResult()==null
進行判斷便可,然而這種方式只能處理單線程的流程圖
(2)對於fork/join的流程來講,並非只有一個流程實例表中並不是只有一個流程實例,還有「子流程實例」,使用task.getExecutionId()的方法獲取到的流程實例頗有多是「子流程實例」,若是任務完成了,那麼「自流程實例」的狀態就會被置爲「inactive」,即非活躍的狀態,這時候再使用(1)中的方法查詢到的結果必定是null;解決方法就是使用「主流程實例」的id來判斷,不管「子流程實例」的狀態怎麼變化,只要「主流程實例」沒有結束,那麼當前流程實例就沒有結束。該id保存到了Form對象中,因此使用以下方式判斷流程實例是否已經結束更爲準確(1)中的方法廢棄不用:
this.processEngine.getExecutionService().createProcessInstanceQuery().processInstanceId(form.getProcessId()).uniqueResult()==null
完整處理的代碼:
1 HttpServletRequest request=ServletActionContext.getRequest(); 2 String fid=request.getParameter("fid"); 3 String taskId=request.getParameter("taskId"); 4 String comment=request.getParameter("comment"); 5 Form form=formService.getFormByFormId(fid); 6 Task task=processEngine.getTaskService().getTask(taskId); 7 8 Map<String,String> variables=new HashMap<String,String>(); 9 variables.put("comment_"+task.getAssignee(), comment); 10 processEngine.getTaskService().setVariables(taskId, variables); 11 processEngine.getTaskService().completeTask(taskId); 12 this.message="1"; 13 //怎麼判斷是否還有後續任務 14 ExecutionService executionService=processEngine.getExecutionService(); 15 ProcessInstanceQuery processInstanceQuery=executionService.createProcessInstanceQuery(); 16 //這裏根據form的processId來判斷比較好 17 ProcessInstanceQuery processInstanceQuery2=processInstanceQuery.processInstanceId(form.getProcessId()); 18 List<ProcessInstance> processInstances=processInstanceQuery2.list(); 19 System.out.println("獲得的流程數量:"+processInstances.size()); 20 task.getExecutionId(); 21 if(this.processEngine.getExecutionService().createProcessInstanceQuery().processInstanceId(form.getProcessId()).uniqueResult()==null){ 22 form.setState(FormState.WANCHENGSHENQING); 23 formService.updateForm(form); 24 } 25 return "ajax"; 26
5.如何顯示流程圖(不包括當前任務節點)
好比在顯示全部流程定義的時候,使用到了「查看流程圖」的連接,單擊該鏈接,就彈框顯示出了一張流程圖。
思路:單擊超連接->觸發單擊事件->調用window.open方法顯示彈框->彈框頁面有img標籤,動態請求後臺圖片->後臺從數據庫中獲取圖片返回輸出流->顯示圖片
(1)整個過程當中有一個參數在各個頁面中一直傳遞着,流程實例ID,使用processId來表示,該參數不斷的在前臺和後臺之間傳遞。
(2)前臺觸發點擊事件處理代碼:
1 $().ready(function(){ 2 $("a").each(function(){ 3 if($(this).text()=="查看流程圖"){ 4 $(this).unbind("click"); 5 $(this).bind("click",function(){ 6 var left=(screen.width-800)/2; 7 var top=(screen.height-500)/2; 9 window.open("PDManagementAction_showImageUI?"+$(this).attr("href").split("?")[1],"_blank","height=500,width=800,left="+left+",top="+top+",scrollbars=1",false); 10 return false; 11 }); 12 } 13 }); 14 });
(3)通過Action轉發,跳轉到showImageUI.jsp頁面:
<img alt="流程定義圖片" src="PDManagementAction_showProcessImageByPdid.action?processId=<s:property value='%{#processId}'/>">
(4)請求了PDManagementAction的showProcessImageByPdid方法,方法中根據processId獲取圖片的輸入流,並通過stream攔截器寫回到輸出流中,這一步由struts2完成,我只須要獲取到輸入流便可。
1 //顯示流程圖的方法 2 public String showProcessImageByPdid() throws Exception{ 3 HttpServletRequest request=ServletActionContext.getRequest(); 4 String processId=request.getParameter("processId"); 5 InputStream is=this.pdManagementService.getImageInputStreamByPDID(processId); 6 this.is=is; 7 return "stream"; 8 }
注意,Action中必定要提供一個輸入流InputStream屬性,而且提供getInputStream方法和setInputStream方法,方法名必定要徹底一致,不能是getIs和setIs!(一般會寫成InputStream is,使用快捷見生成的get、set方法就會變成getIs和setIs)
注意,Action的配置文件必定是這種形式:
<!-- 類型爲stream,由流攔截器來處理後續事項,result標籤中沒有內容 --> <result name="stream" type="stream"></result>
(5)顯示圖片
6.如何顯示流程實例審批到哪裏了
這個功能是一個很是有意思的功能,顯示流程圖的過程和5中的過程如出一轍,關鍵是怎麼使用紅色矩形標識出當前的任務節點,值得一提的是,當前任務節點多是一個,也有多是多個,在作的時候必須都考慮到。
畫圖使用的技術十分簡單,就是CSS,使用CSS的絕對定位功能+DIV塊便可
(1)分析過程:單擊顯示流程週轉圖超連接->觸發事件單擊事件->調用window.open方法打開新窗口,這時候會同時發生兩件事情
* CSS繪製矩形到當前的任務節點
* img標籤發起請求請求流程圖。
而後最後顯示兩步疊加的圖片,以下圖所示:
(2)首先標籤超連接須要傳遞processId,即流程實例標識號參數
<s:if test="%{state!='完成申請'}"> <s:a action="FormAction_showCurrentActivityImage.action"> <s:param name="processId" value="%{processId}"></s:param> 查看流轉記錄圖
</s:a> </s:if>
(3)單擊響應方法:打開了一個新窗口
1 $().ready(function(){ 2 $("a").each(function(){ 3 if($(this).text()=="查看流轉記錄圖"){ 4 $(this).unbind("click"); 5 $(this).bind("click",function(){ 6 var left=(screen.width-800)/2; 7 var top=(screen.height-500)/2; 9 window.open("FormAction_showImageUI.action?"+$(this).attr("href").split("?")[1],"_blank","height=500,width=800,left="+left+",top="+top+",scrollbars=1",false); 10 return false; 11 }); 12 } 13 }); 14 });
(4)在打開新窗口以前首先通過Action預配置數據,這裏是獲取活動節點的數量、位置、長度、寬度的關鍵;這裏使用了一個自定義的Rectangle將這些數據進行了封裝,最終返回了一個封裝了這些數據的List結合。
1 public String showImageUI() throws Exception{ 2 HttpServletRequest request=ServletActionContext.getRequest(); 3 String processId=request.getParameter("processId"); 4 ProcessInstance processInstance=processEngine.getExecutionService().createProcessInstanceQuery().processInstanceId(processId).uniqueResult(); 5 Set<String> names=processInstance.findActiveActivityNames(); 6 List<Rectangle>rectanglelist=new ArrayList<Rectangle>(); 7 for(String name:names){ 8 ActivityCoordinates ac=this.processEngine.getRepositoryService().getActivityCoordinates(processInstance.getProcessDefinitionId(), name); 9 Rectangle rectangle=new Rectangle(); 10 rectangle.setX(ac.getX()); 11 rectangle.setY(ac.getY()); 12 rectangle.setHeight(ac.getHeight()); 13 rectangle.setWidth(ac.getWidth()); 14 rectanglelist.add(rectangle); 15 System.out.println(rectangle); 16 } 17 ActionContext.getContext().put("processId", processId); //請求流程圖使用 18 ActionContext.getContext().put("rectanglelist", rectanglelist); //繪製活動節點矩形框使用 19 return "showImageUI"; 20 }
(5)數據被轉發到了新窗口,新窗口同時進行兩個任務
* 繪製活動節點矩形框:這裏使用struts2標籤進行遍歷建立DIV塊,並使用CSS的定位功能對DIV的衛士進行了設定;設置DIV的邊框爲3px 紅色。
1 <s:iterator value="%{#rectanglelist}"> 2 <div style="position:absolute;border:3px solid red;left:<s:property value="%{x+10}"/>;top:<s:property value="%{y+10}"/>;height:<s:property value="%{height-10}"/>;width:<s:property value="%{width-10}"/>;"> 3 4 </div> 5 </s:iterator>
* 請求流程圖
<img alt="顯示活動的節點圖片" src="FormAction_showCurrentActivityImage.action?processId=<s:property value='%{#processId}'/>">
該頁面的完整代碼爲:
1 <body> 2 <img alt="顯示活動的節點圖片" src="FormAction_showCurrentActivityImage.action?processId=<s:property value='%{#processId}'/>"> 3 <s:iterator value="%{#rectanglelist}"> 4 <div style="position:absolute;border:3px solid red;left:<s:property value="%{x+10}"/>;top:<s:property value="%{y+10}"/>;height:<s:property value="%{height-10}"/>;width:<s:property value="%{width-10}"/>;"> 5 6 </div> 7 </s:iterator> 8 </body>
(6)顯示審批的進度流程圖。
7.如何顯示最高版本的流程定義
使用ProcessDefinitionQuery的orderAsc方法進行升序排序,而後使用Map對象依次添加便可。低版本的最終會被高版本的「沖掉」
1 //查找最高版本的流程定義是關鍵 2 @Override 3 public Set<ProcessDefinition> listAllPDs() { 4 List<ProcessDefinition> processDefinitions=processEngine.getRepositoryService() 5 .createProcessDefinitionQuery() 6 .orderAsc(ProcessDefinitionQuery.PROPERTY_VERSION) //根據Version進行升序排序 7 .list(); 8 Map<String,ProcessDefinition> map=new HashMap<String,ProcessDefinition>(); 9 for(ProcessDefinition processDefinition:processDefinitions){ 10 map.put(processDefinition.getKey(), processDefinition); 11 System.out.println(processDefinition.getId()); 12 } 13 Set<ProcessDefinition> processDefinitions2=new HashSet<ProcessDefinition>(map.values()); 14 return processDefinitions2; 15 }
JBPM整合SSH的項目實戰結束。