I learned a lesson on working with scoped managed beans yesterday. Since I may not be the only one to not know how to handle this I thought I would share. If you know already then just bear with me...
Ok, let us first set the scene:
This bean is set up in the faces-config.xml file as:
<managed-bean>
<managed-bean-name>Configuration</managed-bean-name>
<managed-bean-class>dk.dtu.aqua.catchlog.bean.ConfigurationBean</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
So the ConfigurationBean class will be loaded on first reference and then live as long as the application is active. There are two pieces of code within that class that are important for what I want to demonstrate. First the definition of "meta" which is the Java object that I want to cache and use in various places in my code:
private final MetaDataBean meta = new MetaDataBean();
This object is instantiated when the ConfigurationBean is instantiated. Then I have a "getCurrentInstance()" method to get a hold on the managed bean itself from other places in my code:
public static ConfigurationBean getCurrentInstance() {
// This is a neat way to get a handle on the instance of this bean in the application scope from other Java code...
FacesContext context = Util.getFacesContext();
ConfigurationBean bean = (ConfigurationBean) context.getApplication().getVariableResolver().resolveVariable(context, "Configuration");
return bean;
}
In short what it does is to use a variable resolver to find the object in the application scope known as "Configuration" (which we told was the name of the instance of the managed bean in faces-config.xml).
So we are all set now. I can use the "meta" class in my code like:
Person person = ConfigurationBean.getCurrentInstance().getMeta().getPersonFacade().findPerson(key);
This code simply gets to the ConfigurationBean in the application scope and uses the meta object already instantiated in the ConfigurationBean to get a facade object to find a person in the database based on a key. So I can load the meta data once and reference it again as long as my ConfigurationBean lives. This is an excellent place to implement a caching method....
Ok, if you are like me then you may think that you don't want to write the whole line above to get to the meta data. So what I did was create a base class that implemented the following code:
private MetaDataBean meta = null;
protected MetaDataBean getMeta() {
if (null == meta) {
meta = ConfigurationBean.getCurrentInstance().getMeta();
}
return meta;
}
... and then I could just use this code in my class (which extended the base class):
Person person = getMeta().getPersonFacade().findPerson(key);
Same code, right? No.... - what happens in this last situation is that I now create an instance of "meta" EVERY time I call the "getMeta()" method. And that was NOT my intention - on the contrary. This is due to serialization - all of the objects above are serializable - and they should be when they are (or are referenced from) managed beans...
I found out because I have a "debug" statement in the constructor of my classes that prints a line when the class is instantiated. Very handy in this case.... - otherwise I may not have found out there was a problem yet.
To find out where it was instantiated the "extra times" I added these statements to the constructor of my MetaDataBean:
Throwable t = new Throwable();
t.printStackTrace();
This produces a stack trace that will show you what code caused the extra instantiations of the class. That should put you on the right track to find the culprit.
So what to do? Well there are two solutions to this:
Just always grap the object from the ConfigurationBean and you are fine. In this case the "getMeta()" in our base class would look like this:
protected MetaDataBean getMeta() {
return ConfigurationBean.getCurrentInstance().getMeta();
}
Add the keyword "transient" to the definition of the local "meta" declaration. Then you tell serialization to not try to serialize the object and just ignore it. In this case our base class would look like this:
private transient MetaDataBean meta = null;
protected MetaDataBean getMeta() {
if (null == meta) {
meta = ConfigurationBean.getCurrentInstance().getMeta();
}
return meta;
}
You could see another use case for the "transient" keyword in this video by Tim Tripcony on value binding. And finally thanks to Nathan Freeman for the help to nail down this issue.