How and when is a view scope bean removed from LRU cache in JSF?

2.4k views Asked by At

I have read an excellent answer when view scoped bean is destroyed. (see How and when is a @ViewScoped bean destroyed in JSF?) and I automatically assumed that destroyed bean is also removed from the view scope cache. But I could see that bean is still in the cache, so I would like to know if destroyed view scoped bean should be also removed from the LRU view scope cache, if ever?

In our application we open all details in sepeare tabs/windows. After some opening/closing (depends on numberOfViewsInSession) we could see ViewExpiredException in case when the first detail window is still opened and user has been opening and closing another detail windows and after some time he wants to do some operation in the first window. I have done some debugging and I can see that closed view wasn't removed from the LRU cache.

So is it expected behaviour or is there something wrong in my code? And if it is expected behaviour, is there any useful strategy how to work with multitabs/multiwindow without lot of ViewExpiredException caused by the LRU cache?. I know I can change numberOfViewsInSession but it is the last choice that I want to use.

I prepared a simple testcase and when I'm opening/closing view.xhtml more times, I can see that LRUMap is growing.

Environment : JDK7, mojarra 2.2.4, tomcat 7.0.47

Thanks in advance

view.xhtml

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html">
    <head>
        <title>View Bean</title>
        <meta charset="UTF-8"/>
    </head>
    <body>
        <h:form id="viewForm">
        <div>#{viewBean.text}</div>
        <h:commandButton id="closeButton" value="Close" action="/ClosePage.xhtml"/>
        </h:form>
    </body>
</html>

index.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:head>
        <title>Session bean</title>
    </h:head>
    <h:body>
        <h:form id="sessionForm">
            <h:outputText value="#{sessionBean.text}"/>
            <br/>
            <h:link id="linkView" value="Open view.xhmtl" outcome="/view.xhtml" target="_blank"/>
        </h:form>
    </h:body>
</html>

ClosePage.xhmtl

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">               
    <head><title>Window will be closed</title></head>
    <body>
        <script>window.close();</script>
    </body>
</html>

ViewBean.java

package com.mycompany.mavenproject2;

import com.sun.faces.util.LRUMap;
import java.io.Serializable;
import java.util.Map;
import javax.annotation.PreDestroy;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.context.FacesContext;

@ManagedBean(name = "viewBean")
@ViewScoped
public class ViewBean implements Serializable {

    private static final long serialVersionUID = 13920902390329L;

    private int lruMapSize;

    /**
     * Creates a new instance of ViewBean
     */
    public ViewBean() {

        Map<String, Object> sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
        LRUMap<String, LRUMap> lruMap = (LRUMap) sessionMap.get("com.sun.faces.renderkit.ServerSideStateHelper.LogicalViewMap");
        lruMapSize = lruMap == null ? 0 : lruMap.size();
    }

    @PreDestroy
    void destroyed() {
        System.out.println("View bean destroyed");
    }

    public String getText() {
        return "ViewBean LRU cache size:" + Integer.toString(lruMapSize);
    }

} 

SessionBean.java

package com.mycompany.mavenproject2;

import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean(name = "sessionBean")
@SessionScoped
public class SessionBean implements Serializable {
    private static final long serialVersionUID = 1777489347L;

    /**
     * Creates a new instance of SessionBean
     */
    public SessionBean() {
    }

    public String getText() {
        return "Session bean text";
    }

}
1

There are 1 answers

0
codeturner On BEST ANSWER

I think every JSF developer runs up against this eventually. The true problem lies in the fact that you can't devise a truly reliable stateful system in which the browser will signal back to the ViewScoped bean that it is done with the page, allowing the backing bean to destroy itself. This is why JSF implementations have LRU caches to limit the memory used by a session, which is a great catch-all solution for everyday apps.

There are a few cases in which you know that you are done with the ViewScoped bean, such as a redirect from that bean. For these, you could write your own view handler to perform a smarter caching system, but that's not a trivial task, and frankly, not worth the effort.

The simplest solution I came up with is to use a javascript timer to execute an ajax postback to the server on every page with a ViewScoped bean. (Setting this timer to execute every 30 seconds seems reasonable.) This will move the ViewScoped bean(s) associated with the page to the bottom of the LRU cache, ensuring they aren't expired.

In particular, I use a primefaces poll component to pull this off and stick in a template to be used by all ViewScoped beans. By placing this component in its own form, the request size remains small.