View scope: java.io.NotSerializableException: java

2020-04-16 06:12发布

问题:

There is an error each time a button calls an action from the backing-bean. Only applies to beans with a view scope and I haven't found a way to fix it without regression over other modules in the code.

DefaultFacele E   Exiting serializeView - Could not serialize state: javax.faces.component.html.HtmlInputText 
java.io.NotSerializableException: javax.faces.component.html.HtmlInputText
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)

Or also:

com.ibm.ws.webcontainer.servlet.ServletWrapper service SRVE0014E: Uncaught service() exception 
root cause Faces Servlet: ServletException: /jspFiles/jsf/Deployments/editQueue.faces No saved view state could be found for the view identifier /jspFiles/jsf/Deployments/editQueue.faces 
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:205) 
Caused by: javax.faces.application.ViewExpiredException: /jspFiles/jsf/Deployments/editQueue.faces No saved view state could be found for the view identifier:  /jspFiles/jsf/Deployments/editQueue.faces
    at org.apache.myfaces.lifecycle.RestoreViewExecutor.execute (RestoreViewExecutor.java:128)

faces-config.xml

<managed-bean>
  <managed-bean-name>pc_EditQueue</managed-bean-name>
  <managed-bean-class>pagecode.jspFiles.jsf.deployments.EditQueue</managed-bean-class>
  <managed-bean-scope>view</managed-bean-scope>
  <managed-property>
    <property-name>queueDeploymentBean</property-name>
    <value>#{queueDeploymentBean}</value>
  </managed-property>
</managed-bean>

web.xml

<context-param>
  <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
  <param-value>server</param-value>
</context-param>
<context-param>
  <param-name>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</param-name>
  <param-value>true</param-value>
</context-param>

bean:

@ManagedBean
@ViewScoped
public class EditQueue extends PageCodeBase implements Serializable {
  private static final long serialVersionUID = -1L;
  public String doButtonAddAction() {
    // calls manager (long)
    FacesUtil.setViewMapValue("queueDeploymentBean", queueDeploymentBean);
    return "";
}

I read this suggestion to set SERIALIZE_STATE_IN_SESSION to false and indeed this solution works for this view scope bean. However this fix comes at a high cost: many existing modules in the application don't work anymore so I cannot use this fix there. Some of the regression observed are:

// returns null must be changed with FacesUtil.getSessionMapValue("userId"); 
getSessionScope().get("userId");`

// returns null must be changed with FacesUtil.getViewMapValue("linkerBean");
linkerBean = (Linker) getManagedBean("linkerBean");`

// NPE so must be changed with FacesContext.getCurrentInstance().addMessage(...)
getFacesContext().addMessage(...)`

So my questions are:

  1. why the NotSerializableException even though the bean implements Serializable ?
  2. is there a way to apply the SERIALIZE_STATE_IN_SESSION param over only a subset of the beans or not ?
  3. is there another solution to have my view scope bean to work (without having to change them to request scope or else) ?

    WebSphere 8.0.0.3, Java 1.6.0, JSF 2.0, RichFaces 4.2.3.Final

回答1:

why the NotSerializableException even though the bean implements Serializable ?

Not only the bean needs to be serializable, but all of its properties (and all their nested properties etc) must also be serializable. The name of the offending non-serializable class can easily be found in the exception message:

java.io.NotSerializableException: javax.faces.component.html.HtmlInputText
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)

This suggests that you're binding a <h:inputText> component to the bean like below:

<h:inputText binding="#{bean.fooInput}" ...>
private UIComponent fooInput;

This is indeed illegal when the bean is not in request scope. UIComponent instances are request scoped and may not be shared across multiple requests. Moreover, UIComponent instances are not serializable. Only their state is, but JSF will worry about that all by itself.

You must remove the fooInput property and you need to look for a different solution for the problem for which you incorrectly thought that binding the component to a view scoped bean would be the right solution.

  • If you intend to access it elsewhere in the view, e.g. #{bean.fooInput.value}, then just bind it to the Facelet scope without the need for a bean property:

    <h:inputText binding="#{fooInput}" ...>
    

    It'll be available elsewhere in the same view via #{fooInput.xxx}.

    <h:inputText ... required="#{empty fooInput.value}" />
    

  • If you intend to set some component attribute programmatically inside the bean, e.g. fooInput.setStyleClass("someClass"), or fooInput.setDisabled(true), then you should be binding the specific attribute in the view instead of the whole component:

    <h:inputText ... styleClass="#{bean.styleClass}" />
    ...
    <h:inputText ... disabled="#{bean.disabled}" />
    

  • If you are absolutely positive that you need to get a hand of whole UIComponent instance in the bean for whatever reason, then manually grab it in method local scope instead of binding it:

    public void someMethod() {
        UIViewRoot view = FacesContext.getCurrentInstance().getViewRoot();
        UIComponent fooInput = view.findComponent("formId:fooInputId");
        // ...
    }
    

    But better ask a question or search for an answer how to solve the concrete problem differently without the need to grab a whole component in the backing bean.

See also:

  • How does the 'binding' attribute work in JSF? When and how should it be used?

As to the ViewExpiredException, this has different grounds which is further elaborated in javax.faces.application.ViewExpiredException: View could not be restored.