Sydney
Sydney

Reputation: 12212

JSF 2.0 Injecting managed bean with a different scope

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

Answers (2)

BalusC
BalusC

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

DWoldrich
DWoldrich

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

Related Questions