JSF中有很多区分value
属性和binding
属性的材料。
我对这两种方法的不同之处很感兴趣。给定:
public class User {
private String name;
private UICommand link;
// Getters and setters omitted.
}
<h:form>
<h:commandLink binding="#{user.link}" value="#{user.name}" />
</h:form>
当指定value
属性时会发生什么,这是非常直接的。getter运行以返回User
bean的name
属性值。该值将打印为HTML输出。
但我不明白binding
是怎么工作的。生成的HTML如何维护与User
bean的link
属性的绑定?
以下是手动美化和注释后生成的输出的相关部分(注意idj_id_jsp_1847466274_1
是自动生成的,并且有两个隐藏的输入小部件)。我使用的是Sun的JSF RI 1.2版本。
<form action="/TestJSF/main.jsf" enctype="application/x-www-form-urlencoded"
id="j_id_jsp_1847466274_1" method="post" name="j_id_jsp_1847466274_1">
<input name="j_id_jsp_1847466274_1" type="hidden" value="j_id_jsp_1847466274_1">
<a href="#" onclick="...">Name</a>
<input autocomplete="off" id="javax.faces.ViewState" name="javax.faces.ViewState"
type="hidden" value="-908991273579182886:-7278326187282654551">
</form>
binding
存放在哪里?
它是如何工作的
当构建/恢复JSF视图(Facelets/JSP文件)时,将生成一个JSF组件树。此时,即视图构建时,将评估所有binding
属性(以及id
属性和像JSTL这样的标记处理程序)。当JSF组件在添加到组件树之前需要创建时,JSF将检查binding
属性是否返回预创建的组件(即非null
),如果返回,则使用它;通常的方式";并以自动创建的组件实例为自变量调用CCD_ 15属性后面的setter。
实际上,它将组件树中组件实例的引用绑定到一个作用域变量。这些信息在生成的组件本身的HTML表示中是不可见的。无论如何,这些信息都与生成的HTML输出无关。当提交表单并恢复视图时,JSF组件树将从头开始重建,所有binding
属性都将像上面一段中描述的那样重新评估。在重新创建组件树之后,JSF将把JSF视图状态恢复到组件树中。
组件实例是请求范围的
重要的是要知道和理解具体的组件实例是有效的请求范围。它们在每个请求中都是新创建的,并且它们的属性在恢复视图阶段填充了JSF视图状态中的值。因此,如果您将组件绑定到backingbean的属性,那么backingbean绝对不应该在比请求范围更宽的范围内。另请参阅JSF 2.0规范第3.1.5章:
3.1.5组件绑定
组件绑定通常与JavaBeans结合使用,JavaBeans通过ManagedBean创建工具(参见第5.8.1节"VariableResolver和默认VariableResolve")非常强烈建议应用程序开发人员将组件绑定表达式指向的托管bean放置在"请求"范围这是因为将它放在会话或应用程序范围中需要线程安全,因为UIComponent实例依赖于在单个线程内部运行。对在"会话"范围中放置组件绑定时的内存管理。
否则,组件实例在多个请求之间共享,可能导致";重复部件ID";错误和";怪异的";行为,因为视图中声明的验证器、转换器和侦听器被重新附加到先前请求的现有组件实例。症状很明显:它们被执行多次,每次请求都在组件绑定的同一范围内执行一次
而且,在高负载下(即,当多个不同的HTTP请求(线程)同时访问和操作同一个组件实例时),您可能迟早会面临应用程序崩溃,例如UIComponent.popComponentFromEL处的线程被卡住,或者JSF saveState()期间HashMap中的线程被卡在100%CPU利用率,甚至一些";奇怪的";直接来自JSF实现源代码的IndexOutOfBoundsException
或ConcurrentModificationException
,而JSF正忙于保存或恢复视图状态(即堆栈跟踪指示saveState()
或restoreState()
方法等)。
此外,由于单个组件基本上是通过getParent()
和getChildren()
引用整个组件树的其余部分,所以当将单个组件绑定到视图或会话范围的bean时,您实际上是在HTTP会话中免费保存整个JSF组件树。当视图中有相对较多的组件时,就可用服务器内存而言,这将非常昂贵。
当然,这一切可能可以通过在所有地方添加synchronized
来确保JSF组件的线程安全来解决,但它们永远不会在不同的浏览器选项卡/窗口/会话之间共享,因为这对最终用户来说只会以"结束;wtf"行为,而且这会极大地降低表现。
在bean属性上使用binding
是不好的做法
将整个组件实例绑定到bean属性,即使是在请求范围的bean上,在正确设计的JSF应用程序中也是一种非常罕见的用例,通常也不是最佳实践。它表示有设计气味。通常,您在视图端声明组件,并将它们的运行时属性(如value
)以及其他属性(如styleClass
、disabled
、rendered
等)绑定到正常的bean属性。然后,您只需精确地操作所需的bean属性,而不是获取整个组件并调用与该属性关联的setter方法。
在组件需要";"动态构建";基于静态模型,如果需要,最好在标记文件中使用像JSTL这样的视图构建时间标记,而不是createComponent()
、new SomeComponent()
、getChildren().add()
等等。另请参阅如何将旧JSP的代码片段重构为JSF等效代码?
或者,如果一个部件需要";动态渲染";基于动态模型,然后只需使用迭代器组件(<ui:repeat>
、<h:dataTable>
等)。另请参阅如何动态添加JSF组件。
复合组件则完全不同。将<cc:implementation>
内部的组件绑定到支持组件(即<cc:interface componentType>
标识的组件)是完全合法的。另请参阅a.o.Split java.util.Date over two h:inputText字段,用f:convertDateTime表示小时和分钟,以及如何用JSF 2.0复合组件实现动态列表?
仅在本地作用域中使用binding
然而,有时您希望了解特定组件内部不同组件的状态,这在与操作/值相关的验证相关的用例中更为常见。为此,可以使用binding
属性,但不能与bean属性结合使用。您只需在binding
属性中指定本地EL作用域中的唯一变量名(如binding="#{foo}"
),并且组件在渲染响应期间直接位于同一视图中的其他位置,作为#{foo}
可用的UIComponent
引用。以下是几个相关的问题,在回答中使用了这样的解决方案:
-
只有在按下特定命令按钮时才根据需要验证输入
-
如何仅在未渲染另一个组件的情况下渲染该组件?
-
JSF 2数据表没有数据的行索引模型
-
Primefaces依赖于selectOneMenu和required=";真";
-
当至少有一个字段填写时,根据需要验证一组字段
-
验证失败时,如何更改inputfield和标签的css类?
-
使用Javascript 获取JSF定义的组件
-
使用EL表达式将组件ID传递给JSF 中的复合组件
(这只是上个月的…)
但我需要尽快修复现有的火车残骸
如果您有一个现有的JSF应用程序,其中binding
在大于请求的范围内滥用引用bean,并且您只想在尽可能短的时间内修复它,以修复严重的内存和线程安全问题,那么您最好的选择是(regex)find&更换以下形式的getter和setter:
public SomeComponent getSomeComponent() {
return someComponent;
}
public void setSomeComponent(SomeComponent someComponent) {
this.someComponent = someComponent;
}
转换为以下形式:
public SomeComponent getSomeComponent() {
return getBoundComponent("someComponent");
}
public void setSomeComponent(SomeComponent someComponent) {
setBoundComponent("someComponent", someComponent);
}
使用以下辅助方法,这些方法基本上将它们保存在请求范围中:
protected static <C extends UIComponent> C getBoundComponent(String key) {
return (C) getBoundComponents().get(key);
}
protected static <C extends UIComponent> void setBoundComponent(String key, C component) {
getBoundComponents().put(key, component);
}
private static <C extends UIComponent> Map<String, C> getBoundComponents() {
return (Map<String, C>) FacesContext.getCurrentInstance().getExternalContext().getRequestMap()
.computeIfAbsent("com.example.BOUND_COMPONENTS", $ -> new HashMap<>());
}
然后让IDE(auto-)删除未使用的字段。
另请参阅:
- 如何正确使用JSF中的组件绑定?(会话作用域bean中的请求作用域组件)
- 查看范围:java.io.NotSerializableException:javax.faces.component.html.HtmlInputText
- Binding属性导致在视图中发现重复的组件ID
每个JSF组件都将自己呈现为HTML,并完全控制它生成的HTML。JSF可以使用许多技巧,具体使用哪种技巧取决于您正在使用的JSF实现。
- 确保每个from输入都有一个完全唯一的名称,这样当表单提交回呈现它的组件树时,就可以很容易地判断每个组件可以在哪里读取其值表单
- JSF组件可以生成提交回serer的javascript,生成的javascript也知道每个组件绑定在哪里,因为它是由组件生成的
-
对于像链接这样的内容,您可以将绑定信息作为查询参数、url本身的一部分或matrx参数包含在url中。例如。
CCD_ 43将允许jsf在组件树中查看链接123被点击。也可以是
htp:..../jsf;LinkId=123
回答这个问题的最简单方法是创建一个只有一个链接的JSF页面,然后检查它生成的html输出。这样,使用您正在使用的JSF版本,您将确切地知道这是如何发生的。