为什么这个 ViewScoped 托管 bean 不工作,我怎样才能让它工作?

Why is this ViewScoped managed bean not working and how can I make it work?

这是我的列表页面:

<h:dataTable value="#{actorSearchBacking.all}" var="actor">
    <h:column>
        <f:facet name="header">
            First Name
        </f:facet>
        #{actor.firstname}
    </h:column>
    <h:column>
        <f:facet name="header">
            Last Name
        </f:facet>
        #{actor.lastname}
    </h:column>
    <h:column>
        <h:form>
            <h:commandButton value="Update Actor" action="pocdetail">
                <f:setPropertyActionListener target="#{actorFormBacking.stupidActor}" value="#{actor}"/>
            </h:commandButton>
        </h:form>
    </h:column>
</h:dataTable>

在我的本地环境中看起来像这样:

这是 pocdetail.xhtml,这是更新演员按钮的操作:

<h:body>
    <h:form id="updateActorForm"
            prependId="false">
        <h:inputText id="firstname" value="#{actorFormBacking.stupidActor.firstname}"/>
        <h:inputText id="lastname" value="#{actorFormBacking.stupidActor.lastname}"/>
        <h:commandButton id="updateActorButton"
                         value="Update Actor!"
                         action="#{actorFormBacking.updateActor()}"/>
    </h:form>
</h:body>

最后ActorFormBacking如下:

@ManagedBean
@ViewScoped
public class ActorFormBacking implements Serializable {

    private Actor stupidActor;

    public Actor getStupidActor() {
        return stupidActor;
    }

    public void setStupidActor(Actor stupidActor) {
        this.stupidActor = stupidActor;
    }
}

当我调试应用程序时,我看到调用了 setStupidActor 并设置了 属性 stupidActor,但是当调用 getter 时,它再次为空。

因为这是一个 ViewScoped bean,我希望 stupidActor 不会为 null 并且我希望看到 pocdetail.xhtml 页面充满值,但我看到的只是空输入文本,因为 stupidActor 是空。

我缺少什么?为什么再次创建 ViewScoped bean 而 属性 为空?

顺便说一句,我正在使用包中的注释:

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

您似乎正在从一个视图导航到另一个视图。换句话说,您销毁了当前视图并创建了一个新视图。从逻辑上讲,视图范围也将被销毁并重新创建,包括所有视图范围内的托管 bean。视图范围内的托管 bean 恰好被两个视图引用并不会改变这种行为。

视图范围内的 bean 与视图本身一样长。就像请求范围内的 bean 与请求本身一样长,等等。为了更好地了解 JSF(和 CDI)中各种范围的生命周期,请前往此问答:How to choose the right bean scope?

然而,功能需求是可以理解的。您想要单独的主从页面并将所选项目从主页面传递到详细页面以进行编辑。有几种方法可以实现这一点:

  1. 规范的方法是只使用可添加书签的 GET link 而不是不可添加书签的 POST link。替换下面的部分

    <h:form>
        <h:commandButton value="Update Actor" action="pocdetail">
            <f:setPropertyActionListener target="#{actorFormBacking.stupidActor}" value="#{actor}"/>
        </h:commandButton>
    </h:form>
    

    由此

    <h:link value="Update Actor" outcome="pocdetail">
        <f:param name="stupidActor" value="#{actor.id}" />
    </h:link>
    

    并在详情页面中,通过作为查询字符串参数传入的标识符获取Actor。此问答中对此进行了详细充实:Creating master-detail pages for entities, how to link them and which bean scope to choose@FacesConverter(forClass) 在这里很有用。


  2. 如果您出于某种原因想要坚持 POST,那么最好的办法是将其存储在请求范围内。

    FacesContext.getCurrentInstance().getExternalContext().getRequestMap().put("stupidActor", stupidActor);
    

    并在同一个 bean

    @PostConstruct 中检索它
    stupidActor = (Actor) FacesContext.getCurrentInstance().getExternalContext().getRequestMap().get("stupidActor");
    

  3. 如果您碰巧使用 CDI,或者愿意使用(我强烈推荐,JSF 2.3.0-m06 中已经弃用了 JSF 托管 bean,另请参阅 Backing beans (@ManagedBean) or CDI Beans (@Named)?), then consider using MyFaces CODI's @ViewAccessScoped。只要所有回发视图明确引用该 bean,具有此范围的 bean 就会存在。一旦您使用 GET 导航出去,或者当导航视图没有在任何地方引用该 bean 时,它就会被销毁。

    @Named
    @ViewAccessScoped
    public class ActorFormBacking implements Serializable {}
    

  4. 将两个视图合并为一个视图,其中包含有条件呈现的主从部分。您可以在此问答中找到启动示例:Recommended JSF 2.0 CRUD frameworks. Or if you happen to use PrimeFaces, .