Debug JSF lifecycle
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 aConverterException and ValidatorException will be thrown.
Well, you probably already know that the JSF lifecycle contains 6 phases:
- Restore view
- Apply request values
- Process validations
- Update model values
- Invoke application
- 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.
Add the following lines to the faces-config.xml to activate the LifeCycleListener.
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
The dummy converter: MyConverter.java
The dummy validator: MyValidator.java
The backing bean: MyBean.java
The minimal faces configuration: faces-config.xml
Back to top
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
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.
Back to top
The form submit
The form submit with the value "test" entered should output at least:
START PHASE RESTORE_VIEW 1
MyBean
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":
The form submit with the value "test" entered should output at least:
START PHASE RESTORE_VIEW 1
MyBean
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 theUIViewRoot, 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 theinputComponent.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 haveimmediate="true" set will just continue the lifecycle as usual.
Back to top
Add immediate="true" to UICommand only
Extend the h:commandButton in the test.jsp with immediate="true" (don't forget to remove from h:inputText):
The form submit with the value "test" entered should output at least:
START PHASE RESTORE_VIEW 1
MyBean
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 theinputComponent.setSubmittedValue(submittedValue) will be executed before the action() method will be executed.
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().
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 frominputComponent.getSubmittedValue() in the action() method. You could even change it usinginputComponent.setSubmittedValue(newValue) in the action() method.
Back to top
Add immediate="true" to UIInput and UICommand
Extend the h:inputText as well as the h:commandButton in the test.jsp with immediate="true":
The form submit with the value "test" entered should output at least:
START PHASE RESTORE_VIEW 1
MyBean
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
MyBean action: succes
END PHASE APPLY_REQUEST_VALUES 2
START PHASE RENDER_RESPONSE 6
MyConverter getAsString: test
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.
Behind the scenes the submitted form values are obtained as request parameters and set in the relevant components in theUIViewRoot, 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 theinputComponent.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. Finally 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.
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. 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 all components with 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 by the relevant components in theUIViewRoot. In this case you can retrieve the input value from inputComponent.getValue() in the action() method. The new input value is also available by the ValueChangeEvent in the inputChanged() method. You could even change it usinginputComponent.setValue(newValue) in the action() method.
Note for other components without immediate: any other UIInput components inside the same form which don't haveimmediate="true" set will not be converted, validated nor updated, but behind the scenes theinputComponent.setSubmittedValue(submittedValue) will be executed before the action() method will be executed. 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.
Back to top
Conversion error
Let's see what happens if a conversion error will occur. Change the getAsObject() method of MyConverter.java as follows (and remove the immediate="true" from the test.jsp file):
The form submit with the value "test" entered should output at least:
START PHASE RESTORE_VIEW 1
MyBean
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
END PHASE PROCESS_VALIDATIONS 3
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.
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, where a ConverterException is thrown. The validator and the valueChangeListener are bypassed. Also the inputComponent.setValue(submittedValue) won't take place. The lifecycle will proceed to the Render response phase immediately.
4. Update model values.
This phase is skipped due to the ConverterException.
5. Invoke application.
This phase is skipped due to the ConverterException.
6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean, expecting the values for which a ConverterException has occurred. Behind the scenes those will be retrieved from the components in the UIViewRoot, e.g.inputComponent.getSubmittedValue().
Note: any other UIInput components inside the same form which don't throw a ConverterException will continue the lifecycleas usual, only the invoke application phase will still be skipped.
Back to top
Validation error
Let's see what happens if a validation error will occur. Change the validate() method of MyValidator.java as follows (and remove the immediate="true" from the test.jsp file and revert MyConverter.java back to original):
The form submit with the value "test" entered should output at least:
START PHASE RESTORE_VIEW 1
MyBean
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
END PHASE PROCESS_VALIDATIONS 3
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.
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 values are retrieved as objects from the components, passed through the converter getAsObject() method and validated by the validator, where a ValidatorException is thrown. The valueChangeListener is bypassed. Also theinputComponent.setValue(submittedValue) won't take place. The lifecycle will proceed to the Render response phase immediately.
4. Update model values.
This phase is skipped due to the ValidatorException.
5. Invoke application.
This phase is skipped due to the ValidatorException.
6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean, expect of the values for which a ValidatorException has occurred. Behind the scenes those will be retrieved from the components in the UIViewRoot, e.g.inputComponent.getSubmittedValue().
Note: any other UIInput components inside the same form which don't throw a ValidatorException will continue the lifecycleas usual, only the invoke application phase will still be skipped.
Back to top
Okay, when should I use the immediate attribute?
If it isn't entirely clear yet, here's a summary, complete with real world use examples when they may be beneficial:
- If set in UIInput(s) only, the process validations phase will be taken place in apply request values phase instead. Use this to prioritize validation for the UIInput component(s) in question. When validation/conversion fails for any of them, the non-immediate components won't be validated/converted.
- If set in UICommand only, the apply request values phase until with update model values phases will be skipped for any of the UIInput component(s). Use this to skip the entire processing of the form. E.g. "Cancel" or "Back" button.
- If set in both UIInput and UICommand components, the apply request values phase until with update model values phases will be skipped for any of the UIInput component(s) which does not have this attribute set. Use this to skip the processing of the entire form expect for certain fields (with immediate). E.g. "Password forgotten" button in a login form with a required and immediate username field and a required but non-immediate password field.
No comments:
Post a Comment