15.5. ConversationContext,Request和Session之间的会话上下文

15.5.1. ManagedBean工作在Session范围下的问题

Web应用建议所有的Bean都工作在Request下工作,而日益复杂的Web应用很难在单个Request中完成所有的任务,因为复杂任务往往涉及多次请求的来回,所以很多情况下,我们只能使用Session范围下工作的ManagedBean维持各个请求间的数据关联。

在Session下工作的ManagedBean,同样带来难以解决的问题,比如存在并发访问的问题,因为共用的同一个数据保存于Session,难免会有多线程同时访问该变量,引起并发问题;同时,数据量大时内存消耗极大,Session带来的痛苦也难以接受。

OperaMasks之前的版本,部分构件也必须要工作在Session范围下的ManagedBean里,如DataGrid和Tree的二次取数等,取数请求为无状态请求,不提交表单的值,导致在二次取数时无法根据用户的查询条件(如输入域的值)进行数据的筛选,被迫使用Session范围的ManagedBean来保存数据。

15.5.2. ConversationContext的产生及其工作原理

针对以上的问题,ConversationContext需要在两次请求间保存值,并且ConversationContext又不能保存太长,所以ConversationContext的存活时间由用户根据自己应用的具体需求来决定其存活时间,同时提供在特殊页面上对ConversationContext进行激活的构件,使其ConversationContext存活时间更长。

每一个ConversationContext包含一个作为session范围内唯一的id,名为jconversationid,该jconversationid通过引擎带在每一个请求中,使下一个页面可以通过jconversationid获取前页面的ConversationContext,取出保存于ConversationContext中的数据。

15.5.3. ConversationContext的使用

15.5.3.1. 在web.xml中配置CONVERSATION_TIMEOUT参数,参数为Long型,默认为120秒

若不设置该参数,默认情况下ConversationContext的存活时间是120秒

<context-param>
    <param-name>CONVERSATION_TIMEOUT</param-name>
    <param-value>120</param-value>
</context-param>

15.5.3.2. 获取ConversationContext,向其中存入值

ConversationContext cctx = ConversationContext.getCurrentInstance();
cctx.put("test", "operamasks v3");

15.5.3.3. 获取ConversationContext,根据key取出相应的值

ConversationContext cctx = ConversationContext.getCurrentInstance();
Object strValue = cctx.get("test");

15.5.3.4. 在页面中的链接加入jconversationid参数,页面跳转也可以使用ConversationContext

通过EL表达式方式,向页面链接加入jconversationid参数

<a href="http://127.0.0.1:6888/test/test.faces?jconversationid=#{jconversationid} />

15.5.4. ConversationContext示例

下面是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);
   }
}

15.5.5. @SaveState示例

在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();
    }
}

15.5.6. ajax:conversationActivator构件,ConversationContext的定时激活器,定制页面的ConversationContext存活时间

若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就已经没有了。