Web应用建议所有的Bean都工作在Request下工作,而日益复杂的Web应用很难在单个Request中完成所有的任务,因为复杂任务往往涉及多次请求的来回,所以很多情况下,我们只能使用Session范围下工作的ManagedBean维持各个请求间的数据关联。
在Session下工作的ManagedBean,同样带来难以解决的问题,比如存在并发访问的问题,因为共用的同一个数据保存于Session,难免会有多线程同时访问该变量,引起并发问题;同时,数据量大时内存消耗极大,Session带来的痛苦也难以接受。
OperaMasks之前的版本,部分构件也必须要工作在Session范围下的ManagedBean里,如DataGrid和Tree的二次取数等,取数请求为无状态请求,不提交表单的值,导致在二次取数时无法根据用户的查询条件(如输入域的值)进行数据的筛选,被迫使用Session范围的ManagedBean来保存数据。
针对以上的问题,ConversationContext需要在两次请求间保存值,并且ConversationContext又不能保存太长,所以ConversationContext的存活时间由用户根据自己应用的具体需求来决定其存活时间,同时提供在特殊页面上对ConversationContext进行激活的构件,使其ConversationContext存活时间更长。
每一个ConversationContext包含一个作为session范围内唯一的id,名为jconversationid,该jconversationid通过引擎带在每一个请求中,使下一个页面可以通过jconversationid获取前页面的ConversationContext,取出保存于ConversationContext中的数据。
若不设置该参数,默认情况下ConversationContext的存活时间是120秒
<context-param>
<param-name>CONVERSATION_TIMEOUT</param-name>
<param-value>120</param-value>
</context-param>ConversationContext cctx = ConversationContext.getCurrentInstance();
cctx.put("test", "operamasks v3");ConversationContext cctx = ConversationContext.getCurrentInstance();
Object strValue = cctx.get("test");下面是ConversationContext的示例,页面的form里面有两个按键,先点击第一个按键action1,获取ConversationContext实例,并存入值,然后点击第二个按键action2,从ConversationContext中获取第一个请求存入的值并向后台输出。这个示例证明了可以在REQUEST生命周期的LiteBean的多次请求间保持数据。
<w:form>
<w:button label="action1" id="action1"/>
<w:button label="action2" id="action2"/>
</w:form>
@ManagedBean(name="sampleRequestBean",scope=ManagedBeanScope.REQUEST)
public class SampleRequestBean{
@Action
public void action1(){
ConversationContext cctx = ConversationContext.getCurrentInstance();
cctx.put("test", "operamasks v3");
}
@Action
public void action2(){
ConversationContext cctx = ConversationContext.getCurrentInstance();
Object test = cctx.get("test");
System.out.println(test);
}
}在OperaMasks V3.0中,标志了@SaveState注解的域保存在ConversationContext中,使得DataGrid或者Tree的二次取数请求可以获取保存于标志了@SaveState注解的域对应的值:
<w:page title="DataFilter">
<w:form>
<layout:panelGrid columns="5">
<f:verbatim>类型:</f:verbatim>
<w:combo id="type">
<f:selectItem itemLabel="公司名称" itemValue="name" />
<f:selectItem itemLabel="产品" itemValue="product" />
</w:combo>
<f:verbatim>条件:</f:verbatim>
<w:textField id="content" />
<w:button id="search" label="搜索" />
</layout:panelGrid>
</w:form>
<w:dataGrid id="grid" paged="true" rows="10" height="400" width="800">
<w:outputColumn id="id" hidden="true" hidable="false" />
<w:outputColumn id="name" width="200" align="center" sortable="true" header="公司" />
<w:outputColumn id="leadingProduct" header="主打产品" />
<w:outputColumn id="address" header="地址" />
<w:outputColumn id="phone" header="联系电话" />
<w:outputColumn id="homepage" width="250" header="公司主页" />
</w:dataGrid>
</w:page>
通过@SaveState保存type和content值,使得在grid.reload()进行二次取数请求时调用的getCompanys()方法中可以使用type和content的值,用作查询条件来查询。
public class DataFilterBean {
CompanyService companyService = new CompanyService();
@Bind(id = "grid")
private List<Company> getCompanys() {
if (content == null || "".equals(content)) {
return companyService.findAll();
} else if ("name".equals(type)) {
return companyService.findByName(content);
} else if ("product".equals(type)) {
return companyService.findByProduct(content);
}
return null;
}
@Bind(id = "grid")
private UIDataGrid grid;
@Bind(id = "type")
@SaveState
private String type;
@Bind(id = "content")
@SaveState
private String content;
@Action(id = "search")
public void search() {
grid.reload();
}
}若ConversationContext的全局超时时间无法满足某一页面的需求时,即某一页面的ConversationContext需要保存特别长的时间(有可能是后台数据量比较大,需要较长的执行时间,该时间超出了全局配置的时长,默认为120秒),此时使用ConversationActivator定时激活ConversationContext,使其在更长时间内保持状态而不被框架销毁。
ConversationActivator构件API:
delay:时延,单位秒,表示多少秒后启动ConversationActivator
period:激活间隔,单位秒,表示每间隔一段时间激活一次ConversationContext
timeout:超时时间,单位秒,若页面停留时间超过此timeout,则ConversationActivator停止发送激活请求,若timeout设置为-1,默认不限时长
<ajax:conversationActivator delay="50" period="50" timeout="1800"/>
上面的代码意思是说,从页面打开50秒后每隔50秒激活一次ConversationContext,30分钟(1800秒)后不再激活。delay必须小于web.xml中配置的ConversationContext默认存活时间,否则第一次激活之间ConversationContext就已经没有了。