为什么第一个 AJAX 调用会重置我的视图参数



我无法找出为什么第一次 ajax 调用会导致再次调用我的视图参数的 setter,而随后的每个调用都不会再次调用 setter。

我有以下简单的视图豆:

package test;
import java.io.Serializable;
import javax.faces.view.ViewScoped;
import javax.inject.Named;
@Named
@ViewScoped
public class TestController implements Serializable {
private static final long serialVersionUID = 1L;
String param;
public String getParam() {
return param;
}
public void setParam(String param) {
System.out.println("param set to " + param);
this.param = param;
}
}

我还有一个非常基本的.xhtml页面,它只包含一个按钮:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head></h:head>
<f:metadata>
<f:viewParam id="param" name="param" value="#{testController.param}"/>
</f:metadata>
<h:form id="form">
<h:commandButton id="button" value="Test">
<f:ajax execute="@this"></f:ajax>
</h:commandButton>
</h:form>
</html>

现在,在测试此页面时,我在浏览器中调用https://localhost:8443/test/test.xhtml?param=foo。正如我所料,日志声称视图参数设置为"foo"。现在我挣扎的地方是,当我第一次按下按钮时,日志再次声称参数设置为"foo",证明再次调用了setter。我不明白为什么 ajax 请求再次设置视图参数。令我困惑的是,任何后续的按钮单击都不会再次调用视图参数的 setter,尤其是当第一次和所有后续调用看起来完全相同时。

所以我的问题是:

  • 为什么在第一次 ajax 调用时调用视图参数的 setter,而在后续调用中不调用?
  • 有什么方法可以防止这种行为吗?

我正在 Wildfly 19 上运行该示例,该示例使用 Mojarra 2.3.9.SP06,如果这有任何帮助的话。

编辑1:为了更清楚,为什么这个问题与ajax调用后丢失的f:viewParam不同。另一个问题是为什么视图参数在第一次 ajax 调用后丢失以及如何始终发送它们。这个问题问的恰恰相反:为什么视图参数无论如何都要第一次发送,如何防止这种情况?

另一个问题的答案声称可以打电话给FacesContext.getCurrentInstance().isPostback()。我知道这一点。虽然它当然可以在检测 ajax 召回的意义上工作,并且使我在这种情况下不会重置视图参数,但它不会阻止首先调用视图参数的 setter。这就是我理想中想要实现的目标。我至少会满足于理解为什么在第一次 ajax 调用时对视图参数的处理方式不同。我想在概念上有些东西我没有理解。

编辑2:我在 https://github.com/eclipse-ee4j/mojarra/issues/4714 下提交了错误报告。

你没有在概念上误解。我也不明白。

我目前仍在调查为什么在第一个并且仅在第一个 ajax 回调时调用二传器。我本来希望它总是或从不被调用。对@fuggerjaki61的分析在某种程度上是正确的方向,但它似乎与围绕 null 或未提交值的更大问题有关。

在最简单的解决方案中可以阅读大量信息:OmniFaceso:viewParam而不是f:viewParam

并使用

<o:viewParam id="param" name="param" value="#{testController.param}"/>

(不要忘记声明xmlns:o="http://omnifaces.org/ui",但既然你应该;-)无论如何都在使用OmniFaces,我认为它已经存在:-))

从我读到的所有信息中,我认为也许可以设置

<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>

也可能解决它,但它没有。在第一次 ajax 调用中,setter 仍然使用旧值调用,在第二次和后续调用中,如果未提交,它只会显式将值设置为 null。也不是你似乎想要的。

更多详情

@fuggerjaki61的解决方案可能会起作用,但我不确定其他情况下的后果,因为我也可以通过更改其他内容但破坏其他情况来解决此问题。如果我尝试将o:viewParam的基础知识与f:viewParam进行比较,提交的值(如@fuggerjaki61在另一个答案中提到的)确实起作用。它被保存在本地o:viewParam

@Override
public String getSubmittedValue() {
return submittedValue;
}
@Override
public void setSubmittedValue(Object submittedValue) {
this.submittedValue = (String) submittedValue; // Don't delegate to statehelper to keep it stateless.
}

而在 f:viewParam 中,它被读取并设置为状态助手

@Override
public Object getSubmittedValue() {
return getStateHelper().get(PropertyKeys.submittedValue);
}
/**
* PENDING (docs)  Interesting that submitted value isn't saved by the parent
* @param submittedValue The new submitted value
*/
@Override
public void setSubmittedValue(Object submittedValue) {
getStateHelper().put(PropertyKeys.submittedValue, submittedValue);
} 

在这里阅读 java 文档,我个人会说你问题中的"为什么"对我来说,看起来某处有一个错误(或遗漏),尚未确定,但要么是偶然的,要么是由o:viewParam明确解决的

快速解决方案

解决此问题的最佳方法是使用includeViewParams设置为trueo:form(setParam在每个 ajax 请求中调用;仅当参数可以在 ajax 请求中更改时调用)。

@Kukeltje已经说过使用o:viewParam(这与覆盖UIViewParameter相同),因此setParam方法仅在开始时调用一次。


解释

基本上是在对第一个 ajax 请求的初始请求期间保存的参数值。在第一次 ajax 请求之后,该值最终丢失。

理解这一点的最好方法可能是逐阶段分析(查看源代码以了解方法的作用也很有帮助):


初始请求

恢复视图阶段无特定内容

应用请求值阶段:调用decode并使用当前参数值设置rawValue

工艺验证阶段无具体内容

更新模型值阶段:调用setParam,之后UIInput.resetValues()submittedValue设置为 null

调用应用程序阶段无特定内容

呈现响应阶段:使用rawValue调用setSubmittedValue(为 null)(已设置 rawValue;请参阅应用请求值阶段)

第一阿贾克斯

恢复视图阶段:原始值重新初始化为null

应用请求值阶段:调用decode并使用当前参数值设置rawValue(参数值为null)

工艺验证阶段无具体内容

更新模型值阶段:使用设置为null但在渲染响应阶段再次设置的submittedValue调用setParam; 再次调用UIInput.resetValues()submittedValue设置为null

调用应用程序阶段无特定内容

渲染响应阶段:再次调用setSubmittedValue并将其设置为rawValuenull

每个以下 ajax 请求

submittedValuerawValuenull因此破坏了恢复参数值的所有可能性。setParam再也不会被调用。


所有解决方案

  • 重写encodeAll方法以不再执行任何操作,因此UIInput.resetValues()永久重置值(请参阅如何重写组件)
  • 使用o:viewParam(没有rawValue变量)
  • 使用o:form

当参数在 ajax 请求期间没有更改时,前两个解决方案是最好的。


覆盖 UIViewParameter

要覆盖UIViewParameter创建一个扩展UIViewParameter的类并将其添加到faces-config.xml

<component>
<component-type>javax.faces.ViewParameter</component-type>
<component-class>com.example.CustomUIViewParameter</component-class>
</component>

最新更新