UIViewRoot and ViewHandler
The JSF lifecycle has 6 phases. They are RestoreViewPhase, ApplyRequestValuesPhase,ProcessValidationsPhase, UpdateModelValuesPhase, InvokeApplicationPhase, and RenderResponsePhase. The corresponding classes are all in the package com.sun.faces.lifecycle.The RestoreViewPhase gets the UIViewRoot from the facesContext or the viewhandler. Either way, it will eventually set the viewRoot in the facesContext.
The ApplyRequestValuesPhase basically uses the ViewRoot to do decoding:
public void execute(FacesContext facesContext) throws FacesException{ ...... UIComponent component = facesContext.getViewRoot(); ...... component.processDecodes(facesContext); ....... }The ProcessValidationsPhase basically uses the ViewRoot to invoke the validators:
public void execute(FacesContext facesContext) throws FacesException{ ...... UIComponent component = facesContext.getViewRoot(); ...... component.processValidators(facesContext); ....... }The UpdateModelValuesPhase uses the ViewRoot to process updates:
public void execute(FacesContext facesContext) throws FacesException{ ...... UIComponent component = facesContext.getViewRoot(); ...... component.processUpdates(facesContext); ....... }The InvokeApplicationPhase uses the ViewRoot to process application:
public void execute(FacesContext facesContext) throws FacesException{ ...... UIComponent component = facesContext.getViewRoot(); ...... component.processApplication(facesContext); ....... }The RenderResponsePhase uses the viewHandler and the ViewRoot to render the view:
public void execute(FacesContext facesContext) throws FacesException{ ...... facesContext.getApplication().getViewHandler().renderView(facesContext, facesContext.getViewRoot()); ....... }From the above, we can see that the ViewRoot object plays an important role. All the phases between the RestoreViewPhase and the RenderViewPhase just delegate the work to the ViewRoot.
Some Notes on UIViewRoot
The UIViewRoot class calls the broadcast(event) method of the UIComponent class in many places. The name of this method is misleading. If you look at the code, it actually does not just broadcast the event. It actually also invokes the event listeners to perform actions.The following is the method in the UIComponentBase class:
public void broadcast(FacesEvent event) throws AbortProcessingException { if (event == null) { throw new NullPointerException(); } if (listeners == null) { return; } IteratorThere are two concrete event classes that implement the processListener(...) method. One is ActionEvent, and the other is ValueChangeEvent. The ActionEvent class has the following:iter = listeners.iterator(); while (iter.hasNext()) { FacesListener listener = iter.next(); if (event.isAppropriateListener(listener)) { event.processListener(listener); } } }
public void processListener(FacesListener listener) { ((ActionListener) listener).processAction(this); }The ValueChangeEvent class has the following:
public void processListener(FacesListener listener) { ((ValueChangeListener) listener).processValueChange(this); }So here the event actually lets the listeners to act on the event itself. It does not just sit at the listeners.
The following are more details. The com.sun.faces.application.ActionListenerImpl has the code below:
public void processAction(ActionEvent event) { if (logger.isLoggable(Level.FINE)) { logger.fine("processAction(" + event.getComponent().getId() + ")"); } UIComponent source = event.getComponent(); ActionSource actionSource = (ActionSource) source; FacesContext context = FacesContext.getCurrentInstance(); Application application = context.getApplication(); Object invokeResult = null; String outcome = null; MethodBinding binding = null; binding = actionSource.getAction(); if (binding != null) { try { if (null != (invokeResult = binding.invoke(context, null))) { outcome = invokeResult.toString(); } // else, default to null, as assigned above. } catch (MethodNotFoundException e) { if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, e.getMessage(), e); } throw new FacesException (binding.getExpressionString() + ": " + e.getMessage(), e); } catch (EvaluationException e) { if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, e.getMessage(), e); } throw new FacesException (binding.getExpressionString() + ": " + e.getMessage(), e); } } // Retrieve the NavigationHandler instance.. NavigationHandler navHandler = application.getNavigationHandler(); // Invoke nav handling.. navHandler.handleNavigation(context, (null != binding) ? binding.getExpressionString() : null, outcome); // Trigger a switch to Render Response if needed context.renderResponse(); }The javax.faces.event.MethodExpressionValueChangeListener class has the code below:
public void processValueChange(ValueChangeEvent valueChangeEvent) throws AbortProcessingException { if (valueChangeEvent == null) { throw new NullPointerException(); } try { FacesContext context = FacesContext.getCurrentInstance(); ELContext elContext = context.getELContext(); methodExpression.invoke(elContext, new Object[] {valueChangeEvent}); } catch (ELException ee) { throw new AbortProcessingException(ee.getMessage(), ee.getCause()); } }The javax.faces.event.MethodExpressionActionListener class has the code below:
public void processAction(ActionEvent actionEvent) throws AbortProcessingException { if (actionEvent == null) { throw new NullPointerException(); } try { FacesContext context = FacesContext.getCurrentInstance(); ELContext elContext = context.getELContext(); methodExpression.invoke(elContext, new Object[] {actionEvent}); } catch (ELException ee) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "severe.event.exception_invoking_processaction", new Object[]{ ee.getCause().getClass().getName(), methodExpression.getExpressionString(), actionEvent.getComponent().getId() }); StringWriter writer = new StringWriter(1024); ee.getCause().printStackTrace(new PrintWriter(writer)); LOGGER.severe(writer.toString()); } throw new AbortProcessingException(ee.getMessage(), ee.getCause()); } }
A Closer Look at How the InvokeApplicationPhase works
The code of the execute(...) method of the InvokeApplicationPhase is very simple. Essentially it just calls root.processApplication(facesContext) where root is the UIViewRoot. And the essential code in the processApplication(...) method of UIViewRoot is the following:broadcastEvents(context,PhaseId.INVOKE_APPLICATION);So let's see how it works in reality. The example used here is a simple jsp file with a form:
<html> ... <f:view> ... <h:form> ... <h:commandButton action="#{quiz.answerAction}" /> ... </h:form> ... </f:view> </html>Here is what happened during the lifecycle of JSF. We can see when and where the event object is created.
- In the ApplyRequestValuePhase, JSF iterates through all the UIComponents in the UIForm and calls the processDecodes method of each component.
- The CommandButton component gets the Render and lets the Render do the decode.
- The ButtonRender creates an ActionEvent and queues the event to the UIViewRoot. The event has the Invoke_Application phase ID and so will be used in the Invoke_Applicaiton pahse. Interestingly at this point, the events[] field of the UIViewRoot class is still null. So it is created and the ActionEvent is put in. Actually this makes sense because the events[] field in UIViewRoot class is a transient field. So it is not saved in the session and can not be restored in the Restore_View phase.
- In the Invoke_Applicaiton phase, the UIViewRoot will pick up this event and let it work.
No comments:
Post a Comment