jsf-2cdiprerenderview

PreRenderView incrementally called on every postback


I have an issue with the order and number of executions of an f:event type="preRenderView".

During my search here I found as usual answers from BalusC in this and this post related to my problem - still it leaves two questions for me:

  1. When I put one f:event type="preRenderView" in the template file (for managing common tasks like checks about the user state which applies for all my views) and another f:event type="preRenderView" in each view (for handling view specific initializations), I wonder why the listener method from the view is called before the one from the template.

  2. When I put the whole <f:metadata><f:event [..] /></f:metadata> after ui:define as suggested, it gets called twice after redirecting to that page from the login page, but when I put it one level higher after ui:composition it only gets called once.


Update: Example

The following example demonstrates the behaviour above:

This is the template file template_test.xhtml, containing a listener for preRenderViewevent calling a common method in a handler for all views:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xml:lang="de" lang="de" xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
    <link rel="stylesheet" type="text/css" href="../resources/css/style.css" />
</h:head>
<h:body>
    <f:event type="preRenderView" listener="#{testHandler.initCommon()}" />
    <div id="content">
        <ui:insert name="content" />
    </div>
</h:body>
</html>

This is the view file test.xhtml, containing also a listener for preRenderViewevent calling a view specific method in a handler and a command button redirecting via a handler method:

<?xml version="1.0" encoding="UTF-8"?>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    template="template_test.xhtml">
    <ui:define name="content">
        <f:metadata>
            <f:event type="preRenderView"
                listener="#{testHandler.initIndividual()}"></f:event>
        </f:metadata>
        <h:form>
            <h:commandButton value="Redirect" action="#{testHandler.redirect()}" />
        </h:form>
    </ui:define>
</ui:composition>

This is the handler class TestHandler.java containing the 3 methods:

package test;

import java.io.Serializable;

import javax.enterprise.context.SessionScoped;
import javax.inject.Named;

@Named
@SessionScoped
public class TestHandler implements Serializable {

    private static final long serialVersionUID = -2785693292020668741L;

    public void initCommon() {
        System.out.println("Common init called.");
    }

    public void initIndividual() {
        System.out.println("Init for individual page called.");
    }

    public String redirect() {
        return "test/test.xhtml?faces-redirect=true";
    }
}

Now, this is what I see in my tomcat log when requesting the test page:

Init for individual page called.
Common init called.
Init for individual page called.

This shows no. 1, that the event handler from the view is called before the one from the template and no. 2, that the event handler from the view is called twice.

It also shows a 3rd point (that's why I included a button with a redirect to the same page) showing what happens if the page gets requested by redirect - the individual page gets called even more times:

Init for individual page called.
Common init called.
Init for individual page called.
Init for individual page called.

Both no. 2 and 3 can be prevented by either putting the whole metadata section above the ui:define or by adding a dummy parameter to the view's metadata section which is not included in the URL:

<f:metadata>
    <f:viewParam name="dummyToDenySecondRedirect" />
    <f:event type="preRenderView"
        listener="#{testHandler.initIndividual()}"></f:event>
</f:metadata>

Can someone tell me the reason for those cases?


Solution

  • I can reproduce it. This is caused by the presence of /WEB-INF/beans.xml and implicitly thus CDI. It even occurs when you switch back to standard JSF annotations while keeping the beans.xml file. This is already reported as both issue 1771 and issue 2162. However, due to absence of a concrete WAR file reproducing the issue and low votes, the Mojarra developers didn't bother to look closer at it.

    I have reported it once again as issue 2719. The problem can be reproduced with a smaller example:

    <!DOCTYPE html>
    <html lang="en"
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:ui="http://java.sun.com/jsf/facelets"
    >
        <f:metadata>
            <f:event type="preRenderView" listener="#{bean.preRenderView}" />
        </f:metadata>
        <h:head>
            <title>preRenderView fail</title>
        </h:head>
        <h:body>
            <p>On initial request, you'll see the listener being invoked twice instead of only once.</p>
            <p>On every postback by the below button, you'll see the listener being invoked once more.</p>
            <h:form>
                <h:commandButton value="submit" />
            </h:form>
            <p>Note however that this increments every time you issue the postback.</p>
            <p>If you remove <code>/WEB-INF/beans.xml</code> and redeploy, then the issue will disappear.</p>
        </h:body>
    </html>
    

    and

    package com.example;
    
    import javax.faces.bean.ManagedBean;
    import javax.faces.bean.RequestScoped;
    
    @ManagedBean
    @RequestScoped
    public class Bean {
    
        public void preRenderView() {
            System.out.println("preRenderView called");
        }
    
    }