Reputation: 12212
I have a controller that is stateless which takes care of processing forms. This is defined as ApplicationScoped
. On my page I have a form associated to a backing bean defined as a ViewScoped
.
The error I got when I want to process the form:
serverError: class com.sun.faces.mgbean.ManagedBeanCreationException Unable to create managed bean myController. The following problems were found:
- The scope of the object referenced by expression #{myFormBean}, view, is shorter than the referring managed beans (myController) scope of application
In my form:
Name: <h:inputText value="#{myFormBean.name}" id="name" />
<h:commandButton value="Save Name" action="#{myController.processForm}">
<f:ajax render="nameResult" />
</h:commandButton>
Your name is <h:outputText value="#{myFormBean.name}" id="nameResult"/>
The controller:
@ManagedBean
@ApplicationScoped
public class MyController {
@ManagedProperty("#{myFormBean}")
private MyFormBean myBean;
public void processForm() {
System.out.println(myBean.getName());
// Save current name in session
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(
"name", myBean.getName());
}
}
The backing bean:
@ManagedBean
@ViewScoped
public class MyFormBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I could solve that by setting the controller as SessionScoped but it’s not a clean way since the controller is stateless, so I don’t need one controller for each session. One controller for the whole application should be sufficient.
I have a Spring MVC background, that’s why I am confused on how to do things with JSF 2.0
Upvotes: 4
Views: 15102
Reputation: 1108537
There is a flaw in your design. Your controller is not stateless at all. It has a property which is different for each request/view, namely the myBean
. If it was supported, then every new request/view would override the previously set one and the enduser will face the property value of a completely different enduser. This leads to problems in high concurrent situations.
You need to make it request/view scoped instead of application scoped. Still then, I believe that you have to approach it completely different. You're manually setting an attribute in the session scope in the action method instead of setting it as a property of an (injected) session scoped bean. How to solve it properly depends on the functional requirement which is not clear from the question.
Upvotes: 12
Reputation: 4017
I have JSF Managed Beans with different scopes referencing each other, and I have found Spring adequately addresses my needs. The key to success was the Spring AOP that proxifies bean references and gives me more flexible autowiring. I think it would make sense for you to mix JSF and Spring similarly to achieve your goals.
I don't use the JSF scope declaration annotations to declare my beans. Instead, I use Spring to declare my beans, assign their scopes, and specify that I want the oddly-scoped beans to have aop proxies generated for them (so they can get autowired appropriately whenever they are referenced.) I use the spring el-resolver to make my Spring beans addressable as JSF2 managed beans in EL.
I don't use view scope in my program, I use session scope with request-scoped beans referencing them. But, I suspect my approach could be adapted for your view-scoped beans as well.
I don't use annotations to declare my beans, I use XML for declaring my beans and their scopes. I just find it handy to have all of my bean declarations cataloged in one place. I'm sure there's a pure annotation-based approach to achieve what I've got. I do use the @Autowired annotation in my beans to indicate where references to other beans should be wired in. This keeps my XML configuration short, eliminates the need for getter/setters, and gives me a little more Java-side flexibility than I've been able to get from going pure XML.
Finally, I gave myself a custom "SmartSession" scope. This is essentially just like session scope, except, it re-autowires every time a bean is pulled out of session (this guards against bean replicas appearing unwired in a failover scenario in a cluster.)
I have come to the conculsion that for session- (and I presume view-) scoped beans to work, you need to make the bean Serializable and mark any @Autowired fields as transient. The SmartSession gives me the confidence in that context to be assured that I stay autowired even in exceptional cases. I based my SmartSession custom scope idea off of this answer: Initialize already created objects in Spring as well as internet sources for how to write custom scopes.
Here's some code snippets to hopefully give you some ideas -
Sample Session-scoped bean:
public class UserProfileContainer implements Serializable {
private static final long serialVersionUID = -6765013004669200867L;
private User userProfile;
public void setUserProfile(User userProfile) {
this.userProfile = userProfile;
}
public User getUserProfile() {
return this.userProfile;
}
}
Bean that references my smartSession-scoped bean:
public class KidProfileEditor implements Serializable {
private static final long serialVersionUID = 1552049926125644314L;
private String screenName;
private String password;
private String confirmPassword;
private String firstName;
private String lastName;
private String city;
private String state;
private String notes;
private String country;
@Autowired
private transient UserProfileContainer userProfileContainer;
}
Snippet from my applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:jms="http://www.springframework.org/schema/jms"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"
default-lazy-init="true" >
<!-- BOILERPLATE magic AOP setup tags -->
<context:annotation-config />
<context:component-scan base-package="com.woldrich.kidcompy" />
<aop:aspectj-autoproxy />
<!-- JSF2+Spring custom scope configurations -->
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="safetySession">
<bean class="com.woldrich.kidcompy.faces.util.SpringSafetySessionScope"/>
</entry>
</map>
</property>
</bean>
<bean id="userProfileContainer" class="com.woldrich.kidcompy.auth.UserProfileContainer" scope="safetySession">
<aop:scoped-proxy />
</bean>
<bean id="kidProfileEditor" class="com.woldrich.kidcompy.faces.actionview.KidProfileEditor" scope="request" />
</beans>
web.xml snippet:
<web-app xsi:schemaLocation="http://java.sun.com/xml/ns/javaee /WEB-INF/includes/schema/web-app_2_5.xsd" id="KidCompy" version="2.5" metadata-complete="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee">
<distributable/>
<context-param>
<description>Allows the Spring Context to load multiple application context files</description>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/mainApplicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
</web-app>
faces-config.xml snippet:
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee /WEB-INF/includes/schema/web-facesconfig_2_0.xsd"
version="2.0">
<application>
<el-resolver>
org.springframework.web.jsf.el.SpringBeanFacesELResolver
</el-resolver>
</application>
</faces-config>
Upvotes: 2