JSF 生命周期 - JSF Liefcycle (一)

JSF 生命周期 - JSF Liefcycle (1)

ZZ.

Code depot of a Java EE developer



Listen and debug JSF lifecycle phases
The JSF lifecycle will be explained and debugged here using the "poor man's debugging" approach with sysout's. We'll also check what happens if you add immediate="true" to the UIInput and UICommand and what happens when a ConverterException and ValidatorException will be thrown.

Well, you probably already know that the JSF lifecycle contains 6 phases:

  • 1.Restore view
  • 2.Apply request values
  • 3.Process validations
  • 4.Update model values
  • 5.Invoke application
  • 6.Render response


You can use a PhaseListener to trace the phases of the JSF lifecycle and execute some processes where required. But you can also use a "dummy" PhaseListener to debug the phases to see what is happening in which phase. Here is a basic example of such a LifeCycleListener:

Note: if you don't have a JSF playground environment setup yet, then you may find this tutorial useful as well: JSF tutorial with Eclipse and Tomcat.

package mypackage;

import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

public class LifeCycleListener implements PhaseListener {

    public PhaseId getPhaseId() {
        return PhaseId.ANY_PHASE;
    }

    public void beforePhase(PhaseEvent event) {
        System.out.println("START PHASE " + event.getPhaseId());
    }

    public void afterPhase(PhaseEvent event) {
        System.out.println("END PHASE " + event.getPhaseId());
    }

}

 

Add the following lines to the faces-config.xml to activate the LifeCycleListener.

<lifecycle>
    <phase-listener>mypackage.LifeCycleListener</phase-listener>
</lifecycle>

 

This produces like the following in the system output:


START PHASE RESTORE_VIEW 1
END PHASE RESTORE_VIEW 1
START PHASE APPLY_REQUEST_VALUES 2
END PHASE APPLY_REQUEST_VALUES 2
START PHASE PROCESS_VALIDATIONS 3
END PHASE PROCESS_VALIDATIONS 3
START PHASE UPDATE_MODEL_VALUES 4
END PHASE UPDATE_MODEL_VALUES 4
START PHASE INVOKE_APPLICATION 5
END PHASE INVOKE_APPLICATION 5
START PHASE RENDER_RESPONSE 6
END PHASE RENDER_RESPONSE 6

 

Basic debug example

To trace all phases of the JSF lifecycle, here is some sample code which represents simple JSF form with a "dummy" converter and validator and the appropriate backing bean. The code sample can be used to give us more insights into the phases of the JSF lifecycle, to understand it and to learn about it.

The minimal contents of the test JSF file: test.jsp

 

<%@taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<f:view>
    <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
            <title>Debug JSF lifecycle</title>
        </head>
        <body>
            <h:form>
                <h:inputText
                    binding="#{myBean.inputComponent}"
                    value="#{myBean.inputValue}"
                    valueChangeListener="#{myBean.inputChanged}">
                    <f:converter converterId="myConverter" />
                    <f:validator validatorId="myValidator" />
                </h:inputText>
                <h:commandButton
                    value="submit"
                    action="#{myBean.action}" />
                <h:outputText
                    binding="#{myBean.outputComponent}"
                    value="#{myBean.outputValue}" />
                <h:messages />
            </h:form>
        </body>
    </html>
</f:view>

 

The dummy converter: MyConverter.java

package mypackage;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

public class MyValidator implements Validator {

    public void validate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException
    {
        System.out.println("MyValidator validate: " + value);
    }

}


package mypackage;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

public class MyValidator implements Validator {

    public void validate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException
    {
        System.out.println("MyValidator validate: " + value);
    }

}


package mypackage;

import javax.faces.component.html.HtmlInputText;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.event.ValueChangeEvent;

public class MyBean {

    // Init ----------------------------------------------------

    private HtmlInputText inputComponent;
    private String inputValue;
    private HtmlOutputText outputComponent;
    private String outputValue;

    // Constructors -------------------------------------------

    public MyBean() {
        log("constructed");
    }

    // Actions ------------------------------------------------

    public void action() {
        outputValue = inputValue;
        log("succes");
    }

    // Getters ------------------------------------------------

    public HtmlInputText getInputComponent() {
        log(inputComponent);
        return inputComponent;
    }

    public String getInputValue() {
        log(inputValue);
        return inputValue;
    }

    public HtmlOutputText getOutputComponent() {
        log(outputComponent);
        return outputComponent;
    }

    public String getOutputValue() {
        log(outputValue);
        return outputValue;
    }

    // Setters ------------------------------------------------

    public void setInputComponent(HtmlInputText inputComponent) {
        log(inputComponent);
        this.inputComponent = inputComponent;
    }

    public void setInputValue(String inputValue) {
        log(inputValue);
        this.inputValue = inputValue;
    }

    public void setOutputComponent(HtmlOutputText outputComponent) {
        log(outputComponent);
        this.outputComponent = outputComponent;
    }

    // Listeners ----------------------------------------------

    public void inputChanged(ValueChangeEvent event) {
        log(event.getOldValue() + " to " + event.getNewValue());
    }

    // Helpers ------------------------------------------------

    private void log(Object object) {
        String methodName = Thread.currentThread().getStackTrace()[2].getMethodName();
        System.out.println("MyBean " + methodName + ": " + object);
    }
}

 

The minimal faces configuration: faces-config.xml

 

 

 

<converter>
    <converter-id>myConverter</converter-id>
    <converter-class>mypackage.MyConverter</converter-class>
</converter>
<validator>
    <validator-id>myValidator</validator-id>
    <validator-class>mypackage.MyValidator</validator-class>
</validator>
<managed-bean>
    <managed-bean-name>myBean</managed-bean-name>
    <managed-bean-class>mypackage.MyBean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
</managed-bean>

 

The first call

The first call in a freshly started webapplication with a fresh session should output at least:

START PHASE RESTORE_VIEW 1
END PHASE RESTORE_VIEW 1
START PHASE RENDER_RESPONSE 6
MyBean <init>: constructed
MyBean getInputComponent: null
MyBean setInputComponent: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean getInputValue: null
MyBean getOutputComponent: null
MyBean setOutputComponent: javax.faces.component.html.HtmlOutputText@13bbca56
MyBean getOutputValue: null
END PHASE RENDER_RESPONSE 6

1. Restore view.
As the session is fresh, there's no means of any UIViewRoot to restore, so nothing to see here.

2. Apply request values.
This phase is skipped because there is no form submit.

3. Process validations.
This phase is skipped because there is no form submit.

4. Update model values.
This phase is skipped because there is no form submit.

5. Invoke application.
This phase is skipped because there is no form submit.

6. Render response.
The bean is constructed. Behind the scenes a new UIViewRoot is created and stored in the session. If the component binding getters returns precreated components (precreated in e.g. the constructor) and not null, then those will be used, otherwise JSF will create new components. The components will be stored in the UIViewRoot and the bounded components are set in the component bindings. The values to be shown are retrieved from the value binding getters in the backing bean. If the values aren't set yet, they defaults to null. The component bindings are not required by the way. Only use them if you actually need the component in the backing bean for other means than getting/setting the value. In this article they are included just to demonstrate what all happens in the lifecycle.

The form submit

The form submit with the value "test" entered should output at least:

START PHASE RESTORE_VIEW 1
MyBean <init>: constructed
MyBean setInputComponent: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputComponent: javax.faces.component.html.HtmlOutputText@13bbca56
END PHASE RESTORE_VIEW 1
START PHASE APPLY_REQUEST_VALUES 2
END PHASE APPLY_REQUEST_VALUES 2
START PHASE PROCESS_VALIDATIONS 3
MyConverter getAsObject: test
MyValidator validate: test
MyBean getInputValue: null
MyBean inputChanged: null to test
END PHASE PROCESS_VALIDATIONS 3
START PHASE UPDATE_MODEL_VALUES 4
MyBean setInputValue: test
END PHASE UPDATE_MODEL_VALUES 4
START PHASE INVOKE_APPLICATION 5
MyBean action: succes
END PHASE INVOKE_APPLICATION 5
START PHASE RENDER_RESPONSE 6
MyBean getInputValue: test
MyConverter getAsString: test
MyBean getOutputValue: test
END PHASE RENDER_RESPONSE 6

1. Restore view.
The bean is constructed. The UIViewRoot is restored from session and the bounded components are set in the component bindings.

2. Apply request values.
Nothing to see here. Behind the scenes the submitted form values are obtained as request parameters and set in the relevant components in the UIViewRoot , for example inputComponent.setSubmittedValue("test" ) .

3. Process validations.
The submitted values are passed through the converter getAsObject() method and validated by the validator. If the conversion and validation succeeds, then the initial input value will be retrieved from the value binding getter and behind the scenes the inputComponent.setValue(submittedValue) and inputComponent.setSubmittedValue(null ) will be executed. If the retrieved initial input value differs from the submitted value, then the valueChangeListener method will be invoked.

4. Update model values.
The converted and validated values will now be set in the value binding setters of the backing bean. E.g. myBean.setInputValue(inputComponent.getValue()) .

5. Invoke application.
The real processing of the form submission happens here.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean. If a converter is definied, then the value will be passed through the converter getAsString() method and the result will be shown in the form.

Back to top

Add immediate="true" to UIInput only

Extend the h:inputText in the test.jsp with immediate="true" :

 

 

 ...
    <h:inputText
        binding="#{myBean.inputComponent}"
        value="#{myBean.inputValue}"
        valueChangeListener="#{myBean.inputChanged}"
        immediate="true">
    ...
 

The form submit with the value "test" entered should output at least:

START PHASE RESTORE_VIEW 1
MyBean <init>: constructed
MyBean setInputComponent: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputComponent: javax.faces.component.html.HtmlOutputText@13bbca56
END PHASE RESTORE_VIEW 1
START PHASE APPLY_REQUEST_VALUES 2
MyConverter getAsObject: test
MyValidator validate: test
MyBean getInputValue: null
MyBean inputChanged: null to test
END PHASE APPLY_REQUEST_VALUES 2
START PHASE PROCESS_VALIDATIONS 3
END PHASE PROCESS_VALIDATIONS 3
START PHASE UPDATE_MODEL_VALUES 4
MyBean setInputValue: test
END PHASE UPDATE_MODEL_VALUES 4
START PHASE INVOKE_APPLICATION 5
MyBean action: succes
END PHASE INVOKE_APPLICATION 5
START PHASE RENDER_RESPONSE 6
MyBean getInputValue: test
MyConverter getAsString: test
MyBean getOutputValue: test
END PHASE RENDER_RESPONSE 6

 

 

1. Restore view.
The bean is constructed. The UIViewRoot is restored from session and the bounded components are set in the component bindings.

2. Apply request values.
Behind the scenes the submitted form values are obtained as request parameters and set in the relevant components in the UIViewRoot , for example inputComponent.setSubmittedValue("test" ) . The submitted values are immediately passed through the converter getAsObject() method and validated by the validator. If the conversion and validation succeeds, then the initial input value will be retrieved from the value binding getter and behind the scenes the inputComponent.setValue(submittedValue) and inputComponent.setSubmittedValue(null ) will be executed. If the retrieved initial input value differs from the submitted value, then the valueChangeListener method will be invoked. This all happens in this phase instead of the Process validations phase due to the immediate="true" in the h:inputText .

3. Process validations.
Nothing to see here. The conversion and validation is already processed in the Apply request values phase, before the values being put in the components. This is due to the immediate="true" in the h:inputText .

4. Update model values.
The converted and validated values will now be set in the value binding setters of the backing bean. E.g. myBean.setInputValue(inputComponent.getValue()) .

5. Invoke application.
The real processing of the form submission happens here.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean. If a converter is definied, then the value will be passed through the converter getAsString() method and the result will be shown in the form.

Note for other components without immediate : any other UIInput components inside the same form which don't have immediate="true" set will just continue the lifecycle as usual .

 

 

Add immediate="true" to UIInput only

1. Restore view.
The bean is constructed. The UIViewRoot is restored from session and the bounded components are set in the component bindings.

 

2. Apply request values.
Behind the scenes the submitted form values are obtained as request parameters and set in the relevant components in the UIViewRoot , for example inputComponent.setSubmittedValue("test" ) . The submitted values are immediately passed through the converter getAsObject() method and validated by the validator. If the conversion and validation succeeds, then the initial input value will be retrieved from the value binding getter and behind the scenes the inputComponent.setValue(submittedValue) and inputComponent.setSubmittedValue(null ) will be executed. If the retrieved initial input value differs from the submitted value, then the valueChangeListener method will be invoked. This all happens in this phase instead of the Process validations phase due to the immediate="true" in the h:inputText .

 

验证与转化提到第二阶段,inputComponent.setValue(submittedValue) and inputComponent.setSubmittedValue(null ) 会被执行!

 

3. Process validations.
Nothing to see here. The conversion and validation is already processed in the Apply request values phase, before the values being put in the components. This is due to the immediate="true" in the h:inputText .

4. Update model values.
The converted and validated values will now be set in the value binding setters of the backing bean. E.g. myBean.setInputValue(inputComponent.getValue()) .

5. Invoke application.
The real processing of the form submission happens here.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean. If a converter is definied, then the value will be passed through the converter getAsString() method and the result will be shown in the form.

Note for other components without immediate : any other UIInput components inside the same form which don't have immediate="true" set will just continue the lifecycle as usual .

 

 

 

Add immediate="true" to UICommand only

 ...
    <h:commandButton
        value="submit"
        action="#{myBean.action}"
        immediate="true" />
    ...
 

The form submit with the value "test" entered should output at least:

START PHASE RESTORE_VIEW 1
MyBean <init>: constructed
MyBean setInputComponent: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputComponent: javax.faces.component.html.HtmlOutputText@13bbca56
END PHASE RESTORE_VIEW 1
START PHASE APPLY_REQUEST_VALUES 2
MyBean action: succes
END PHASE APPLY_REQUEST_VALUES 2
START PHASE RENDER_RESPONSE 6
MyBean getOutputValue: null
END PHASE RENDER_RESPONSE 6

1. Restore view.
The bean is constructed. The UIViewRoot is restored from session and the bounded components are set in the component bindings.

2. Apply request values.
The real processing of the form submission happens here. This happens in this phase instead of the Invoke application phase due to the immediate="true" in the h:commandButton . The UIInput components which don't have immediate="true" set will not be converted, validated nor updated, but behind the scenes the inputComponent.setSubmittedValue(submittedValue) will be executed before the action() method will be executed.

Action 会在这个阶段执行!

3. Process validations.
This phase is skipped due to the immediate="true" in the h:commandButton .

4. Update model values.
This phase is skipped due to the immediate="true" in the h:commandButton .

5. Invoke application.
This phase is skipped due to the immediate="true" in the h:commandButton .

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean, expect for the UIInput components which don't have immediate="true" set. Behind the scenes those will be retrieved from the components in the UIViewRoot, e.g. inputComponent.getSubmittedValue() .

输入 <h:inputText,的值是从inputComponent.getSubmittedValue()得到的。可以在action中去改它,变成一个新value.那UI显示就会成updated后的值,如!

 public void action() {
    	String temp = (String)inputComponent.getSubmittedValue();
    	System.out.println(" --> Action getSubmittedValue: "+temp);
    	inputComponent.setSubmittedValue(temp+" _updated");
    	temp = (String)inputComponent.getValue();
    	System.out.println(" --> Action getValue: "+temp);
    	
        outputValue = inputValue;
        log("succes");
    }

 那在UI 上就成了:aaaaa _updated...

 

Note for all components without immediate : as the Update model values phase is skipped, the value bindings aren't been set and the value binding getters will return null. But the values are still available as submitted value of the relevant components in the UIViewRoot . In this case you can retrieve the non-converted and non-validated input value from inputComponent.getSubmittedValue() in the action() method. You could even change it using inputComponent.setSubmittedValue(newValue) in the action() method.