seam的对话conversaction问题
对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方法。
最后我们还剩下一些长对话没有关闭,这必须要操作者的参与才能完成的,就像操作者打开来多个窗口,他必须手工来关闭,我们可以在工作区中的对话列表上增加关闭对话的操作,就像操作者要关闭窗口一样。