ViewScoped bean getting constructed on every request... part 99 [duplicate]
Asked Answered
B

1

3

ARGH... This seems to have a hundred answers and I haven't found one that works for me, so I guess I will actually ask it again. Here is my scenario:

My site technically has a single page whose contents get swapped out rather than having multiple pages that you navigate to. The starting point is this chunk:

<?xml version="1.0" encoding="UTF-8"?>
<f:view xmlns:f="http://java.sun.com/jsf/core"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets">
  <h:head />
  <h:body>
    <ui:include src="resourceInclude.xhtml" />
    <ui:include src="main.xhtml" />
  </h:body>
</f:view>

The resourceInclude.xhtml includes my css file:

<?xml version="1.0" encoding="UTF-8"?>
<ui:composition xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets">
  <h:outputStylesheet library="css" name="test.css" target="head" />
</ui:composition>

And main.xhtml is the view:

<?xml version="1.0" encoding="UTF-8"?>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:h="http://java.sun.com/jsf/html">
  <h:panelGroup styleClass="test-container" layout="block">
    <h:form id="main-form">
      <h:panelGroup styleClass="test-header" layout="block">
        <h:panelGroup styleClass="navigation" layout="block">
          <ul>
            <li><h:commandLink action="#{viewSelector.setModeHome}">
                <h:outputText value="Home" />
              </h:commandLink></li>
            <li><h:commandLink action="#{viewSelector.setModeReports}">
                <h:outputText value="ASAP Reports" />
              </h:commandLink></li>
            <li><h:commandLink action="#{viewSelector.setModeSupport}">
                <h:outputText value="Technical Support" />
              </h:commandLink></li>
            <li><h:commandLink action="#{viewSelector.setModeHelp}">
                <h:outputText value="Help" />
              </h:commandLink></li>
          </ul>
        </h:panelGroup>
      </h:panelGroup>
      <h:panelGroup styleClass="test-content" layout="block">
        <ui:include src="#{viewSelector.modeName}-view.xhtml" />
      </h:panelGroup>
      <h:panelGroup styleClass="test-footer" layout="block">
        <h:messages />
      </h:panelGroup>
    </h:form>
  </h:panelGroup>
</ui:composition>

It consists of three h:panelGroups. The first is a set of four general navigation links, each link changes the viewSelector.modeName value which is used to include the contents in the second h:panelGroup thusly <ui:include src="#{viewSelector.modeName}-view.xhtml" />. I have stripped this down for this example so each view is basically this:

<?xml version="1.0" encoding="UTF-8"?>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:h="http://java.sun.com/jsf/html">
  <h:panelGroup styleClass="test-home-view">
    <p>home</p>
  </h:panelGroup>
</ui:composition>

The third h:panelGroup is a footer for all the messages to debug what is going wrong.

Anyway, every time I click one of the navigation links, the constructor of the viewSelector bean gets called. This is what my viewSelector bean looks like:

package org.mitre.asias.aires.controller;


import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;


import org.apache.log4j.Logger;


@ManagedBean( name="viewSelector" )
@ViewScoped
public class ViewSelector {
    protected static Logger log = Logger.getLogger( ViewSelector.class );
    private Mode mode = Mode.HOME;
    public static final String PORTLET_NAME = "Test";

    public static enum Mode {
        HOME(1, "home"),
        REPORTS(2, "reports"),
        SUPPORT(3, "support"),
        HELP(4, "help");

        private int value;
        private String name;

        private Mode( int value, String name ) {
            this.value = value;
            this.name = name;
        }

        public int getValue() {
            return value;
        }

        public String getName() {
            return name;
        }
    }

    public ViewSelector() {
        log.trace( "constructing new ViewSelector" );
    }

    public Mode getMode() {
        log.trace( "getting mode" );

        return mode;
    }

    public String getModeName() {
        log.debug( "in getmodename" );
        return getMode().getName();
    }

    public String getPortletName() {
        return PORTLET_NAME;
    }

    public boolean isModeReports() {
        return getMode() == Mode.REPORTS;
    }

    public void setMode( Mode mode ) {
        this.mode = mode;
    }

    public void setModeHelp() {
        setMode( Mode.HELP );
    }

    public void setModeHome() {
        setMode( mode = Mode.HOME );
    }

    public void setModeReports() {
        setMode( mode = Mode.REPORTS );
    }

    public void setModeSupport() {
        setMode( mode = Mode.SUPPORT );
    }
}

I know I must be doing something the wrong way, or else I missing something central as to how JSF works. Any Input?

Basal answered 16/9, 2011 at 20:9 Comment(0)
E
3

The EL in <ui:include src> is causing that.

If disabling the partial state saving in web.xml as per issue 1492 is not an option,

<context-param>
    <param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
    <param-value>true</param-value>
</context-param>

then you need to replace

<ui:include src="#{viewSelector.modeName}-view.xhtml" />

by something like

<h:panelGroup rendered="#{viewSelector.mode == 'HOME'}">
    <ui:include src="home-view.xhtml" />
</h:panelGroup>
<h:panelGroup rendered="#{viewSelector.mode == 'REPORTS'}">
    <ui:include src="reports-view.xhtml" />
</h:panelGroup>
<h:panelGroup rendered="#{viewSelector.mode == 'SUPPORT'}">
    <ui:include src="support-view.xhtml" />
</h:panelGroup>
<h:panelGroup rendered="#{viewSelector.mode == 'HELP'}">
    <ui:include src="help-view.xhtml" />
</h:panelGroup>

A similar question has at least been asked once before :)

Epizoic answered 16/9, 2011 at 20:43 Comment(7)
Once again, thanks. You've gotta get tired of hearing that :). Does EL in any ui element cause this, or just the ui:include? in other words if i switched to templating instead, would i still run into this issue? Oh, and I promise to do more searching next time...Basal
It was hard to find though, also for me :) Luckily I recalled the subject so that I could just find it more easily. As to the problem, this will only occur when you bind an attribute of any tag handler by EL to a view scoped bean. Tag handlers runs during view build time instead of view render time. Tag handlers are all JSTL <c:xxx> tags, some JSF <f:xxx> tags and a few Facelets <ui:xxx> tags. In this (related) blog you can find them all in a nice table: ninthavenue.com.au/blog/c:foreach-vs-ui:repeat-in-faceletsEpizoic
By coincidence, I was last weeks busy writing a "Communication in JSF 2.0" blog article which I've posted just a hour ago. There's a chapter which handles this particular problem in a bit more detail: balusc.blogspot.com/2011/09/…Epizoic
I switched to your suggestion with the h:panelGroup rendered="#{el}", but have a problem. I have a second section that also has 4 modes, one of which is the active mode. In that, the 4th mode relies on a variable set in the third mode and can only be navigated to from that third mode. However, the rendered does not seem to prevent the ui:include from including the page. This makes sense as ui:include gets processed to build the page and rendered would only get checked during render. Is there a way to prevent ui:include?Basal
This is indeed an awkward side effect of this chicken-egg issue :/ Is disabling the partial state saving not an option? In theory, this could be solved by @CustomScoped which holds the view scoped beans straight in the session scope instead of the view state. If I have time left later the day I will experiment with it.Epizoic
Breaking this case out into a minimal project shows that I cannot reproduce. This must mean my assumption is wrong. The ui:include inside of the h:panelGroup rendered="false" does not appear to get called in my test case. I am not sure how my actual project differs so I will post an edit in my comment with more details.Basal
I have created a minimal test case that demonstrates this issue. I have posted it as a new question here. I have the example as a maven project if you want to take a look (though all the code is also posted in the linked to question)Basal

© 2022 - 2024 — McMap. All rights reserved.