對seam的研究使用已經有很長一段時間了,整體感覺是相當的不錯,雖說中間也碰到了各種各樣、大大小小的問題,但最終也都能一一解決了,逐漸對它的信心越來越堅定了。
對話是seam的一個亮點,seam很多内置元件也是利用了對話的特性,比如message,持久化,重定向等等很多都是利用對話來實作的。下面來說說我們項目中使用對話的一些概況。
1)清單翻頁後編輯後再傳回目前頁
2)類似多視窗的工作區切換,和1)相似就是任何時候進入指定頁面都能保留最後一次在該頁面操作的條件(描述的不太好)
這些功能不用seam對話也可是做的,想必大家也為此做過不少工作,應該說是相當麻煩的,并且每個查詢條件都要特定來寫。而使用了對話那就是非常的簡單了,跟具體的清單沒有關系。
大家也都知道對話是seam的一大特色,但對它強大的功能表示懷疑,對它的第一懷疑就是性能問題,而性能是web系統的最大忌諱,是以就将信将疑地浮雲掠過。主要還是對對話這種方式不太習慣,因為這是seam特有的機制,基于session和request之間,還要就是對話的原理還是比較複雜的,需要慢慢的來了解深入。
對話的基本原理:對話範圍的對象按照“對話id+變量名稱”來存儲在session中,每個jsf請求的第一階段都會去恢複目前對話及它的父對話,這樣中間的階段就可以使用前一個request中的對話範圍的對象,在請求的第6階段回去儲存目前長久對話,并且結束所有逾時的長對話。
對話最關鍵的就是要明确的開始和結束,如果不明确的開始對話,那就與普通的request沒有什麼差別;還要明确的來結束對話,當然你也可以不結束對話而是讓他自然逾時,但是這樣明顯會帶來性能問題。
seam提供了多種開始結束(嵌套算是子對話的開始)的方式
1)第一階段,通過s:link s:button标簽來開始、嵌套、結束對話,他們的本質就是通過在url中傳遞cid或對話傳播類型來使用、結束、開始對話等
沒有propagation:目前cid
propagation="none" 傳播類型和cid都沒有
propagation="end":Propagation=end&cid=25
propagation="begin":pagation=begin'
propagation="nest":conversationPropagation=nest&cid=25
propagation="join":Propagation=join&cid=29
propagation="join22":錯誤的傳播類型,傳播類型和cid都沒有
由于s:link s:button最終也是生成html,通過url傳遞cid或對話傳播類型的,是以我們也可以在普通的連結上添加pagation=begin'參數來啟動長對話,這對幀結構的菜單非常有用,這樣我們就可以在幀中友善地使用對話了
2)在第5階段,通過action來開始結束對話,可以直接調用對話方法 #{conversation.begin},也可以調用普通方法,隻是該方法需要@Begin注釋而已.
3)在第6階段,通過page.xml配置檔案來開始結束對話<begin-conversation/> <end-conversation/>注意,同一頁面配置begin-和end-隻能配置一個。
我的感覺最友善的應該是第1種方式,畢竟直接在頁面上寫要直接的多,第3種可能會導緻對話的多長執行,因為進入該頁面時需要執行一次對話,而faces送出時還要執行對話,而我們可能faces送出時并不想執行對話。
一個對話是由多個request組成的,我們大部分的概念是一連串的request,比如使用者注冊分成了多個步驟,還有就是複雜的頁面流,很多朋友一提到對話就與頁面流聯系上,說我們沒有那麼複雜的頁面流程用不上對話,其實頁面流隻是使用對話機制來實作的,而不是對話隻能用在頁面流上,我們項目中使用對話來解決的問題都跟頁面流無關。其實一個對話也可以不連續,一個對話的中間也可以執行其他的對話。就比如使用者注冊的例子,使用者注冊需要3步來完成,而如果操作者執行到第2步卻去執行其他的操作了,這是我們無法控制的,由于web通路的随意性,我們沒辦法來強制使用者的操作。當操作者做完其他事情了,又想繼續剛才的注冊(這裡隻是打個比方,想說明的是一個中斷的任務如何繼續而已),這時如何恢複還沒有結束的對話就顯得非常的必要了。 seam給出的解決方案就是工作區管理,我們可以在頁面上列出所有未結束的長久對話,想繼續那個對話就直接點選對話連結就行了,這樣就進入了該對話最後一次操作時保留的狀态。具體内容請參考滿江紅文檔,裡面的工作區管理寫的還是比較詳細的。
并不是每個沒有結束的長久對話我們都需要恢複的,要做到能恢複我們還必須給對話設定個描述(參見seam文檔),不設定對話描述的對話我們是沒法再使用的,這樣就會産生個問題,那些對話沒法來顯示關閉了,隻能等待對話逾時,不知不覺就會積累出性能問題,因為這些操作可能會非常頻繁,隻開始對話而不結束對話,相當與把很多request的内容都儲存到了session,這是相當可怕的。是以在我們沒有很好的掌握對話的前提下要避免使用對話。還有就是上步提到的工作區管理也存在同樣的問題,由于使用者操作的習慣性(或者誤操作),使用者更習慣于點選注冊菜單來注冊,而不會去工作區裡去恢複中斷的注冊對話,這樣也會産生很多我們不再使用的長對話 。
通過深入研究seam對話,我們通過jsf事件機制來解決了上面兩種問題,保證了對話的及時關閉
不需要操作者通過關閉操作來關閉對話(一般操作者也不會聽你的話去點關閉操作,對他來說這個步驟是多餘的)
public class SeamConversationPhaseListener implements PhaseListener{
private static final long serialVersionUID = 1L;
public void afterPhase(PhaseEvent event) {
System.out.println("--Conversation.size:"+ConversationEntries.getInstance().size());
}
public void beforePhase(PhaseEvent event) {
if(event.getPhaseId()==PhaseId.RENDER_RESPONSE){
endLongConversationEntryList();
isInLongConversation(Pages.getCurrentViewId());
}
}
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
//将不需要恢複的長對話結束
public void endLongConversationEntryList()
{
String currentConversationId=org.jboss.seam.core.Conversation.instance().getId();
ConversationEntries conversationEntries = ConversationEntries.getInstance();
if (conversationEntries==null)
{
return ;
}
else
{
Set<ConversationEntry> orderedEntries = new TreeSet<ConversationEntry>();
orderedEntries.addAll( conversationEntries.getConversationEntries() );
for ( ConversationEntry entry: orderedEntries )
{
if ( !entry.getId().equals(currentConversationId)&&!entry.isDisplayable() && !Session.instance().isInvalid() )
{
ConversationEntries.instance().removeConversationEntry(entry.getId());
}
}
}
}
//點選需要恢複的長對話菜單會轉到已經存在的長對話,與直接在工作區裡點選對話的效果一樣
public boolean isInLongConversation(String viewid){
ConversationEntries conversationEntries = ConversationEntries.getInstance();
if (conversationEntries==null)
{
return false;
}
else
{
Set<ConversationEntry> orderedEntries = new TreeSet<ConversationEntry>();
orderedEntries.addAll( conversationEntries.getConversationEntries() );
int count=0;
ConversationEntry firstEntry=null;
for ( ConversationEntry entry: orderedEntries )
{
if ( entry.isDisplayable() &&entry.getViewId().equals(viewid)&& !Session.instance().isInvalid() )
{
count++;
if(count==1){
firstEntry=entry;
}else{
ConversationEntries.instance().removeConversationEntry(firstEntry.getId());
entry.select();
return true;
}
}
}
}
return false;
}
}
如果同一功能需要不同的對話,比如同樣的清單查詢我們要保留不同的查詢條件,這樣就要根據具體需求來修改 isInLongConversation方法。
最後我們還剩下一些長對話沒有關閉,這必須要操作者的參與才能完成的,就像操作者打開來多個視窗,他必須手工來關閉,我們可以在工作區中的對話清單上增加關閉對話的操作,就像操作者要關閉視窗一樣。