在 POST 请求后重新创建 ViewScoped bean 时重新执行 f:viewAction

Re-execute f:viewAction when ViewScoped bean is recreated following a POST request

环境:JSF 2.2 和 Mojarra 2.2.12 & CDI ViewScoped beans & javax.faces.STATE_SAVING_METHOD 设置为 client

由于 <f:viewParam ... />,为了正确初始化我的 bean,我想在 ViewScoped bean 重新创建时(重新)执行 <f:viewAction action="#{bean.onLoad}" />(视图被从 LRU 推出,参见 com.sun.faces.numberOfLogicalViews)在 POST 请求之后。

<f:metadata>
    <f:viewParam maxlength="100" name="name" value="#{bean.file}" />
    <f:viewAction action="#{bean.onLoad}"  />
</f:metadata>

<o:form includeRequestParams="true">
     <!-- action can only work if onLoad has been called -->
     <p:commandButton action="#{bean.action}" />
</o:form>

有什么想法吗?

备注:

虽然我没有为 numberOfLogicalView 苦苦挣扎,但我经常需要重新初始化具有较长生命周期的 bean。例如,一个用例是当用户点击 table-view 中的某个实体时(重新)从数据库加载一些详细数据。然后我简单地使用一些initialized-flag,例如

@SomeLongLivingScope
public class MyBean {

    private boolean initialized = false;

    public void preRenderView() {
        if ( initialized ) return;
        try {
             do_init();
        } finally {
            initialized = true;
        }
    }

    public void someDataChangedObserver( @Observes someData ) {
        ...
        initialized = false;
    }
}

使用此模式,您可以将 viewAction 与 postBack="true" 一起使用。

I'm aware of postBack="true" but it's not suitable as bean.onLoad() would be called on every POST request.

您可以只在 onPostback 属性中使用 EL,在其中检查模型值 and/or 请求参数是否存在。

如果需要模型值,则只需检查它是否存在:

<f:metadata>
    <f:viewParam maxlength="100" name="name" value="#{bean.file}" required="true" />
    <f:viewAction action="#{bean.onLoad}" onPostback="#{empty bean.file}" />
</f:metadata>

如果不需要模型值,那么也检查请求参数:

<f:metadata>
    <f:viewParam maxlength="100" name="name" value="#{bean.file}" />
    <f:viewAction action="#{bean.onLoad}" onPostback="#{empty bean.file and not empty param.name}" />
</f:metadata>

I cannot call onLoad() in @PostConstruct method because values have not been set by viewParam yet.

考虑到 <o:form> in your snippet, I see that you're using OmniFaces. The very same utility library offers a CDI @Param 注释的存在是为了在 @PostConstruct 运行之前注入、转换和验证 HTTP 请求参数。

因此整个 <f:viewParam><f:viewAction> 可以替换如下:

@Inject @Param(name="name", validators="javax.faces.Length", validatorAttributes=@Attribute(name="maximum", value="100"))
private String file;

@PostConstruct
public void onLoad() {
    if (!Faces.isValidationFailed()) {
        // ...
    }
}

或者,如果您手边有 Bean 验证(又名 JSR303):

@Inject @Param(name="name") @Size(max=100)
private String file;

@PostConstruct
public void onLoad() {
    if (!Faces.isValidationFailed()) {
        // ...
    }
}