JSF 2.0注入具有不同范围的托管bean

时间:2011-07-25 16:39:55

标签: jsf jsf-2

我有一个无状态的控制器,负责处理表格。这被定义为ApplicationScoped。在我的页面上,我有一个与支持bean关联的表单,定义为ViewScoped

我想处理表单时遇到的错误:

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

以我的形式:

       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"/>

控制器:

@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());
    }
}

支持bean:

@ManagedBean
@ViewScoped
public class MyFormBean {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

我可以通过将控制器设置为SessionScoped来解决这个问题,但由于控制器是无状态的,因此它不是一种干净的方式,所以每个会话都不需要一个控制器。整个应用程序的一个控制器就足够了。

我有一个Spring MVC背景,这就是为什么我对如何使用JSF 2.0做事感到困惑

2 个答案:

答案 0 :(得分:11)

您的设计存在缺陷。您的控制器根本不是无状态的。它有一个不同的属性,每个请求/视图,即myBean。如果它受支持,则每个新请求/视图将覆盖先前设置的请求/视图,并且最终用户将面对完全不同的最终用户的属性值。这导致高并发情况下的问题。

您需要将其作为请求/视图作用域而不是应用程序作用域。然而,我相信你必须完全不同。您在action方法中手动设置会话范围中的属性,而不是将其设置为(注入)会话范围bean的属性。如何正确地解决它取决于功能要求,这个问题不明确。

答案 1 :(得分:2)

我有不同范围的JSF Managed Beans相互引用,我发现Spring充分满足了我的需求。成功的关键是Spring AOP,它代理bean引用并为我提供更灵活的自动装配。我认为将JSF和Spring混合起来以实现目标是有意义的。

我不使用JSF范围声明注释来声明我的bean。相反,我使用Spring来声明我的bean,分配它们的范围,并指定我希望奇怪的bean为它们生成aop代理(因此它们可以在引用时适当地自动装配。)我使用spring el-解析器使我的Spring bean可以作为EL中的JSF2托管bean进行寻址。

我不在程序中使用视图范围,我将会话范围与引用它们的请求范围的bean一起使用。但是,我怀疑我的方法也适用于你的视图范围的bean。

我不使用注释来声明我的bean,我使用XML来声明我的bean及其范围。我发现将所有bean声明编入一个地方都很方便。我确信有一种纯粹的基于注释的方法来实现我所拥有的。我在我的bean中使用@Autowired注释来指示应该连接其他bean的引用。这使我的XML配置保持简短,消除了对getter / setter的需求,并且给了我比Java更多的Java端灵活性已经能够从纯XML中获得。

最后,我给自己定制了一个“SmartSession”范围。这基本上就像会话范围一样,除了每次将bean从会话中拉出时它都会重新自动化(这可以防止在群集中的故障转移方案中出现无线连接的bean副本。)

我已经得出结论,对于session-(并且我假设view-)scoped bean工作,你需要使bean Serializable并将任何@Autowired字段标记为瞬态。 SmartSession让我对这种情况充满信心,即使在特殊情况下我也能保持自动装配。我根据这个答案建立了我的SmartSession自定义范围构思:Initialize already created objects in Spring以及如何编写自定义范围的互联网资源。

这里有一些代码片段,希望能给你一些想法 -

示例会话范围的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;
    }
}

引用我的smartSession-scoped bean的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;
}

来自我的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代码段

<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片段:

<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>