9 Dec 2012

Managed Bean Scopes for Component Binding

Everybody knows that JSF UI components can be bound to some managed bean property using component's binding attribute. Like this:

<af:selectBooleanCheckbox label="CheckBox" id="sbc2"
                  binding="#{BindingBean.checkBox}"/>

And in the managed bean java code we can work with this property in a convenient way. But there are some dangerous underwater rocks connected to this feature. The ground for these rocks is hidden in the scope of the managed beans. There are lots of various posts and book articles with strong recommendations not to use managed beans for component bindings with lifespan longer than request. And they are right.
Why? The first reason is serialization. When your application works in a cluster environment in the high-availability mode (actually, this is not the only case) the session with all memory scopes higher than request might be serialized in an order to be restored on a different JVM instance. UIComponent is not a serializable class, and you are going to get a serialization exception in case of binding components to the long-living managed beans. Someone can say - "Ok, that's not an issue, I don't use high-availability mode at all, moreover I can mark my managed bean property as a transient one and avoid exceptions in case of an unexpected serialization". But there is one much more dangerous rock in this shallow water. This is memory usage. In a normal healthy JSF application UI component tree built for a view is a weakly reachable object between requests. So, it can be easily collected by the garbage collector if the application server decides to free some memory according to the GC policy. Actually, in most cases GC policy is going to be specially tuned to maintain JSF applications in the best way. It's going to be tuned to work with a big number of short living objects. A property in your managed bean is a hard reference and it prevents the entire UI component tree to be collected by GC when it's really needed. So, the UI component tree is going to eat your memory as long as the manged bean is going to live. Who knows how long will it take? And now multiply this problem by a number of simultaneously working users of your system, used to just close browsers when they are going for lunch.
Off-course, we can play with weak and soft references in our managed beans for the component bindings, but I'm not sure whether our JSF framework can cope with that. Actually, there is one standard solution working in a similar way. But a bit later about that.
 
"All right" - someone can say. "I got it. I will use only request or backingbean scopes for the component binding."

But be careful at this point!!! Think about what exactly you are going to do in your request scoped beans. There is another very dangerous rock on your way to the happy future.

Let's consider an example with a TaskFlow like this one:
At each view we have two identical components - a button and a checkBox:

<af:commandButton text="Set selected" id="cb1"
                  actionListener="#{viewScope.BackingBean.button_actionListener}"
                  partialSubmit="true"/>
<af:selectBooleanCheckbox label="CheckBox" id="sbc2"
                          binding="#{viewScope.BackingBean.checkBox}"/>


Note, that the checkBox is bound to the viewScope managed bean property. We have two views in the task flow, so we expect two different instances of the managed bean.  Let's have a look at the button's action listener:

  public void button_actionListener(ActionEvent actionEvent)
  {
    checkBox.setValue(Boolean.TRUE);
    RequestContext.getCurrentInstance().addPartialTarget(checkBox);
  }

It's very simple. We're making the checkBox selected and add it as a partial target to be rerendered.
Let's check how it works. Start the taskflow:


Press "Set selected":

Press "Go":

And we can see, that everything is ok. On the second view the CheckBox is unselected again. It's bound to the second instance of the managed bean.
And now, after reading this post and many others, we've got aware of problems connected to the component bindning using long-living beans and decided to use a request scope bean. Let's change the scope of our BackingBean to request and run the same application:



Press "Set selected":

And press "Go":
Ooops!!! The checkBox on the second view is selected. The explanation is very simple - the checkBox is bound to the same instance of the BackingBean. We cannot have two instances of the request scope bean during one request. Changing the scope to the backingBeanScope will not solve the issue. We are going to get the same wrong behavior. We cannot have two instances of the backingBean scope bean during one request in one region (except declarative components).
So, how to fix it?
As I mentioned before, there is some standard solution which allows us to use components references in long-living managed beans. You can read more about ComponentReference here.
But unfortunately this class is available starting from JDev 11.1.1.4.0 only.
So, the most common and reliable solution is to do some refactoring. We are going store the state of our components (in our case "selected" state of the check box) in a view scope bean and we are going to bind components (if really need that) in a request scope bean.
So, we have two managed beans:

<managed-bean id="__3">
  <managed-bean-name>BackingBean</managed-bean-name>
  <managed-bean-class>com.cs.blog.mbean.view.BackingBean</managed-bean-class>
  <managed-bean-scope>view</managed-bean-scope>
</managed-bean>
    
<managed-bean id="__31">
  <managed-bean-name>BindingBean</managed-bean-name>
  <managed-bean-class>com.cs.blog.mbean.view.BindingBean</managed-bean-class>
  <managed-bean-scope>view</managed-bean-scope>
  <managed-property id="__4">
  <property-name>backingBean</property-name>
     <property-class>com.cs.blog.mbean.view.BackingBean</property-class>
     <value>#{viewScope.BackingBean}</value>
  </managed-property>
</managed-bean>
  
BackingBean is a view scope bean and it's going to be injected into a request scope bean BindingBean as a property. This BindingBean we are going to use for the component binding:

<af:commandButton text="Set selected" id="cb1"
                  actionListener="#{BindingBean.button_actionListener}"
                  partialSubmit="true"/>
<af:selectBooleanCheckbox label="CheckBox" id="sbc2"
                  value="#{viewScope.BackingBean.selected}"
                  binding="#{BindingBean.checkBox}"/>


Note, that the value (state) of the checkBox is going to be read from the view scope bean.  And now let's have a look at the action listener:

  public void button_actionListener(ActionEvent actionEvent)
  {
    backingBean.setSelected(Boolean.TRUE);
    RequestContext.getCurrentInstance().addPartialTarget(checkBox);
  }


So, we are changing the value of the injected view scope bean (which is going to be read by the checkBox) and refreshing the component.

That's it!

No comments:

Post a Comment

Post Comment