JSF passing view parameters by reference - when object must be instantiated
Let's say I've got a register page & a register confirm page. I enter user details into the register page, navigate to the register confirm page where I can return back to the register page if there are any mistakes.
I'm going to use view parameters to make the registration data available from the register page to the confirm page, and vice versa.
Supposing there are 20 items of data to be moving from page to page, that's a lot of view parameters and a lot of setPropertyActionListeners, especially as all the data is going to end up nicely packaged in a User object.
开发者_如何学CSo what I want to do is input the data on the register page into the properties of a User record and send a reference to it to the register confirm page. What gave me an idea was seeing the BalusC WeakHashMap converter. This is a JSF converter which has a static weak hash map and generates a uuid as the value for a map entry and the object reference as the key. So by specifying this as a converter for f:viewParam you send the uuid in the query string.
This works fine. The issue I have is that on the register page I have to get an instance of a User class with new. Then I can do:
<h:inputText value="#{bean.user.firstname}"/>
(etc...), and pass the user instance as a view parameter. It works fine from the register to the confirm page. The issue is that when I perform the reverse, sending the user reference back to the register page from the confirm page I absolutely cannot prevent the register page backing bean from re-instantiating the user object, after the setter has been called as a result of the view parameter.
So the converter does it's job and retrieves the User object from the hash map, calls setUser() in the backing bean, and then I see the constructor for the User class firing.
I've tried calling new User() from the bean constructor, in @PostConstruct, in a preRenderView (also checking if an ajax request), but nothing I try prevents the work of the view parameter from getting wiped out if new is involved. I'm sure there's a simple solution but I just can't see it right now.
I'd be grateful for any suggestions for how to solve this problem.
The issue I have is that on the register page I have to get an instance of a User class with new.
So what code is initially creating this new User instance then? If you do this in the preRenderView
handler, then you can simply check for null, can't you?
If the view parameter and converter haven't done their job, user
would still be null and you create a new instance. The bean constructor and @PostConstruct
won't do you any good here, since they both run before the view parameter does its thing, but the preRenderView
event is guaranteed to run after it.
@ManagedBean
public class Bean {
private User user;
public void onPreRenderView() {
if (user == null) {
user = new User();
}
}
}
(Something to additionally consider is that the conversation scope
already does exactly what you're trying to do here. This is part of CDI not JSF, but if you're running in a Java EE 6 Web Profile compliant AS (JBoss AS 6 or 7, Glassfish V3, Resin 4, ...) you already have it. Otherwise it's just an extra jar.)
After several attempts over more than a year to find a solid long term solution to this problem, at last! I've found one. The solution comes in the form of the Apache Myfaces CDI extensions project, aka Myfaces CODI.
This provides additional scopes such as the @ViewAccessScoped which ensures that if a bean is referenced by a page then it is available for that page. Also provided is support for conversation groups. In the scenario where I want to pass an object reference from a register page to a register confirm page, the confirm page can just access the registerView bean directly on the next request. Alternatively you can @Inject one bean into another and access it on the next request, or use f:setPropertyActionListener from the source page.
Myfaces CODI works fine with Mojarra and also with ajaxified component libraries such as primefaces. The concept is similar to what is provided by Jboss Seam, though I've found the additional scope support to be better thought out and I've tested this on glassfish 3.1.1 with no problems.
If you're using @ManagedBean and scope annotations from the javax.faces.bean package in your code, codi intercepts these annotations and uses it's own CDI based versions, so you can convert to CDI simply by adding codi as a dependency to your project and not changing any code.
For me this is like moving from black and white TV to colour TV, I wish I'd found this stuff sooner.
CODI documentation
精彩评论