JSF2 - 为啥渲染响应不重新渲染组件设置?
Posted
技术标签:
【中文标题】JSF2 - 为啥渲染响应不重新渲染组件设置?【英文标题】:JSF2 - Why does render response not rerender component setting?JSF2 - 为什么渲染响应不重新渲染组件设置? 【发布时间】:2011-03-01 01:06:05 【问题描述】:我遇到以下问题:
在我的视图恢复后,字段验证会导致 JSF 跳到渲染响应阶段(因为必填字段为空)。但是即使呈现当前值(空字符串)以向用户显示他/她没有填写任何内容,也不会执行以下语句:
<c:if test="#empty cc.attrs.fieldValue">
<f:attribute name="style" value="background-color: yellow;"/>
</c:if>
这是错误还是功能?请帮忙。
完整的测试示例(Netbeans 6.8 项目)在这里:http://www.221b.cz/so/JSFTester.zip
来自教程:“如果请求是回发,并且在应用请求值阶段、处理验证阶段或更新模型值阶段遇到错误,则在呈现响应阶段呈现原始页面”(http://java.sun.com/javaee/5/docs/tutorial/doc/bnaqq.html)
这是否意味着如果视图在“恢复视图”阶段恢复,然后任何应用请求/验证/更新模型阶段失败并跳到“渲染响应”,渲染响应仅通过恢复的视图而不对客户端进行任何更改?
托管 Bean (TesterBean.java):
package cz.test;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
@ManagedBean
@RequestScoped
public class TesterBean
// Simple DataStore (in real world EJB)
private static String storedSomeValue = null;
private String someValue;
public TesterBean()
public String storeValue()
storedSomeValue = someValue;
return "index";
public String eraseValue()
storedSomeValue = null;
return "index";
public String getSomeValue()
someValue = storedSomeValue;
return someValue;
public void setSomeValue(String someValue)
this.someValue = someValue;
复合组件(field-component.xhtml):
<?xml version='1.0' encoding='ISO-8859-1' ?>
<!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"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<!-- INTERFACE -->
<composite:interface>
<composite:attribute name="currentBehaviour" type="java.lang.String" required="true"/>
<composite:attribute name="fieldValue" required="true"/>
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<h:panelGrid columns="3">
<c:choose>
<c:when test="#cc.attrs.currentBehaviour == 'READONLY'" >
<h:outputText id="fieldValue" value="#cc.attrs.fieldValue">
</h:outputText>
</c:when>
<c:when test="#cc.attrs.currentBehaviour == 'MANDATORY'" >
<h:inputText id="fieldValue" value="#cc.attrs.fieldValue" required="true">
<f:attribute name="requiredMessage" value="Field is mandatory"/>
<c:if test="#empty cc.attrs.fieldValue">
<f:attribute name="style" value="background-color: yellow;"/>
</c:if>
</h:inputText> *
</c:when>
<c:when test="#cc.attrs.currentBehaviour == 'OPTIONAL'" >
<h:inputText id="fieldValue" value="#cc.attrs.fieldValue">
</h:inputText>
</c:when>
</c:choose>
<h:message for="fieldValue" style="color:red;" />
</h:panelGrid>
</composite:implementation>
页面(index.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:ez="http://java.sun.com/jsf/composite/components">
<h:head>
<title>Testing page</title>
</h:head>
<h:body>
<h:form>
<h:outputText value="Some value:"/>
<ez:field-component currentBehaviour="MANDATORY" fieldValue="#testerBean.someValue"/>
<h:commandButton value="Store" action="#testerBean.storeValue"/>
<h:commandButton value="Erase" action="#testerBean.eraseValue" immediate="true"/>
</h:form>
<br/><br/>
<b>Why is field's background color not set to yellow?</b>
<ol>
<li>NOTICE: Field has yellow background color (mandatory field with no value)</li>
<li>Fill in any value (eg. "Hello") and press Store</li>
<li>NOTICE: Yellow background disappeared (as mandatory field has value)</li>
<li>Clear text in the field and press Store</li>
<li><b>QUESTION: Why is field's background color not set to yellow?</b></li>
<li>Press Erase</li>
<li>NOTICE: Field has yellow background color (mandatory field with no value)</li>
</ol>
</h:body>
按照 Brian 的建议进行编辑 (field-component.xhtml)
<?xml version='1.0' encoding='ISO-8859-1' ?>
<!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"
xmlns:composite="http://java.sun.com/jsf/composite">
<!-- INTERFACE -->
<composite:interface>
<composite:attribute name="currentBehaviour" type="java.lang.String" required="true"/>
<composite:attribute name="fieldValue" required="true"/>
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<h:panelGrid columns="3">
<h:outputText rendered="#cc.attrs.currentBehaviour == 'READONLY'" id="fieldValue1" value="#cc.attrs.fieldValue" />
<h:inputText rendered="#cc.attrs.currentBehaviour == 'MANDATORY'" id="fieldValue2" title="#cc.attrs.fieldValue" value="#cc.attrs.fieldValue" required="true" style="#empty cc.attrs.fieldValue ? 'background-color: yellow;' : ''">
<f:attribute name="requiredMessage" value="Field is mandatory"/>
</h:inputText> *
<h:inputText rendered="#cc.attrs.currentBehaviour == 'OPTIONAL'" id="fieldValue3" value="#cc.attrs.fieldValue"/>
<h:message for="fieldValue" style="color:red;" />
</h:panelGrid>
</composite:implementation>
但即使我摆脱了 JSTL 仍然无法正常工作:-( 似乎只有 value 属性使用 h:inputText 中 http 请求的新值进行了更新,但其余属性并未在 Render Response 阶段重新评估。
【问题讨论】:
【参考方案1】:您使用的<c:choose>
和<c:when>
标签是JSTL 标签,而不是JSF 标签。这意味着它们是在构建时评估的,而不是在渲染时。当您回帖时,不会重新构建组件树,而是重新渲染它,并且不会重新评估 <c:
标记。
使用<h:panelGroup rendered="#">
标记而不是<c:
标记再次尝试您的示例。
查看这篇文章了解更多详情: http://drewdev.blogspot.com/2008/03/build-time-vs-render-time.html
记住您不能让组件“重新出现”在 JSF 表单的回发上,这一点非常重要。这是因为 JSF 组件树不应该在保存其状态和恢复其状态之间进行更改。这一点非常重要,所以让我再说一遍,JSF 组件树不应该在保存状态和恢复状态之间进行更改。
【讨论】:
谢谢布赖恩。我尝试按照您的建议实施 field-component.xhtml。我还阅读了您的博客文章(刚刚对我来说很好但很难)。我用你的建议编辑了这个问题,但它仍然不起作用:-( 我将 title 属性添加到 h:inputText 以表明在渲染响应阶段新值 (cc.attrs.fieldValue) 未应用于 h:inputText 属性。第一次填写“Hello”->Store。字段内容是“你好”,标题是“你好”。然后尝试清除内容->存储。字段内容为空但标题仍然是“你好”。这让我很困惑。 似乎在 http 请求中发送的值仅适用于 h:inputText 的“value”属性,而不适用于 h:inputText 标签的任何其他属性。我对吗?这是正确的行为吗?<f:attribute>
标签设置了 inputText 元素的属性,而不是自定义组件的属性。我认为您可以通过使用 `这两天我做了一些调查和调试,这是我的结果。
首先,我简化了示例以省略复合组件并使其尽可能简单。
托管 bean (TesterBean2.java)
package cz.test;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
@ManagedBean
@RequestScoped
public class TesterBean2
// Simple DataStore (in real world EJB)
private static String storedSomeValue = null;
private String someValue;
public TesterBean2()
public String storeValue()
storedSomeValue = someValue;
return "index";
public String eraseValue()
storedSomeValue = null;
return "index";
public String getSomeValue()
someValue = storedSomeValue;
return someValue;
public void setSomeValue(String someValue)
this.someValue = someValue;
测试页面(index.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">
<h:head>
<title>Testing page</title>
</h:head>
<h:body>
<h:form>
<h:inputText id="fieldValue" requiredMessage="Field is mandatory" title="#testerBean2.someValue" value="#testerBean2.someValue" required="true" style="#empty testerBean2.someValue ? 'background-color: yellow;' : ''"/>
<h:commandButton value="Store" action="#testerBean2.storeValue"/>
<h:commandButton value="Erase" action="#testerBean2.eraseValue"/>
</h:form>
</h:body>
问题出在哪里? 我认为这是 com.sun.faces.renderkit_html_basic.TextRenderer 及其方法 getEndTextToRender 的问题:
protected void getEndTextToRender(FacesContext context,
UIComponent component,
String currentValue)
throws IOException
ResponseWriter writer = context.getResponseWriter();
assert(writer != null);
boolean shouldWriteIdAttribute = false;
boolean isOutput = false;
String style = (String) component.getAttributes().get("style");
String styleClass = (String) component.getAttributes().get("styleClass");
String dir = (String) component.getAttributes().get("dir");
String lang = (String) component.getAttributes().get("lang");
String title = (String) component.getAttributes().get("title");
if (component instanceof UIInput)
writer.startElement("input", component);
writeIdAttributeIfNecessary(context, writer, component);
writer.writeAttribute("type", "text", null);
writer.writeAttribute("name", (component.getClientId(context)),
"clientId");
// only output the autocomplete attribute if the value
// is 'off' since its lack of presence will be interpreted
// as 'on' by the browser
if ("off".equals(component.getAttributes().get("autocomplete")))
writer.writeAttribute("autocomplete",
"off",
"autocomplete");
// render default text specified
if (currentValue != null)
writer.writeAttribute("value", currentValue, "value");
// Rest of code omitted
一个 currentValue 参数被显式传递到从 com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeEnd 调用的方法中
@Override
public void encodeEnd(FacesContext context, UIComponent component)
throws IOException
rendererParamsNotNull(context, component);
if (!shouldEncode(component))
return;
ResponseWriter writer = context.getResponseWriter();
assert(writer != null);
// NOTICE currentValue getter
String currentValue = getCurrentValue(context, component);
if (logger.isLoggable(Level.FINE))
logger.log(Level.FINE,
"Value to be rendered 0",
currentValue);
// NOTICE currentValue
getEndTextToRender(context, component, currentValue);
如果我们仔细看看 getCurrentValue() 方法
/**
* @param context the FacesContext for the current request
* @param component the UIComponent whose value we're interested in
*
* @return the value to be rendered and formats it if required. Sets to
* empty string if value is null.
*/
protected String getCurrentValue(FacesContext context,
UIComponent component)
if (component instanceof UIInput)
Object submittedValue = ((UIInput) component).getSubmittedValue();
if (submittedValue != null)
// value may not be a String...
return submittedValue.toString();
String currentValue = null;
Object currentObj = getValue(component);
if (currentObj != null)
currentValue = getFormattedValue(context, component, currentObj);
return currentValue;
getSubmittedValue() 返回的属性在Restore View 阶段填充(如果Process 验证阶段跳到Render response 阶段)。结果,我们得到了“更新”的值,该值仅从用户传递给 value 属性,其余的保持不变。
如果流程验证阶段成功,不会导致直接跳到渲染响应阶段(如果用户填写任何非空值),则会调用 HtmlInputText 的新构造函数并 样式、标题等。 属性是从头开始填充的。这些属性由托管 bean 填充,该 bean 在阶段更新模型值中使用适当的数据进行了更新。
好的,这不是错误,而是功能。它只是肯定了我的论点,即句子中有异味:“如果请求是回发,并且在应用请求值阶段、流程验证阶段或更新模型值阶段遇到错误,则在渲染响应阶段呈现原始页面” .
如果我真的渴望必填字段的黄色背景,任何线索如何解决这种情况?
更新项目在这里:http://www.221b.cz/so/JSFTester2.zip
【讨论】:
【参考方案3】:我终于设法让验证工作了。
我使用了可以访问 UIComponent 的验证器。如果验证失败,组件会应用特殊样式。在渲染响应阶段也会考虑这种风格。
那么它的行为如何?
-
视图已恢复,包括样式 style="#empty testerBean2.someValue ? 'background-color: yellow;' :''"
验证未通过。因此 testerBean2.someValue 没有更新(因为跳过了更新模型值阶段),但是使用 RequiredValidator 将常量样式设置为 h:inputText - component.setValueExpression("style", new ValueExpressionLiteral("background-color: yellow;", String.班级));
在渲染响应中,即使 testerBean.someValue 尚未更新,也会应用黄色背景,因为必需的验证器已经设置了常量 new ValueExpressionLiteral("background-color: yellow;", String.class)
我已经实现了自己需要的验证器(灵感来自 http://www.codereye.com/2009/12/validating-empty-text-field-using-jsf.html 的 Bashan 验证器)。
RequiredValidator.java
package cz.test;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
import org.apache.el.ValueExpressionLiteral;
public class RequiredValidator implements Validator
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException
if (value == null || "".equals(value.toString().trim()))
FacesMessage message = new FacesMessage();
String messageStr = (String) component.getAttributes().get("message");
if (messageStr == null)
messageStr = "Please enter data";
message.setDetail(messageStr);
message.setSummary(messageStr);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
component.setValueExpression("style", new ValueExpressionLiteral("background-color: yellow;", String.class));
throw new ValidatorException(message);
else
component.setValueExpression("style", new ValueExpressionLiteral("", String.class));
我添加的行: component.setValueExpression("style", new ValueExpressionLiteral("background-color: yellow;", String.class));
强制对空字段 (web.xml) 进行 JSF 触发器验证:
....
<context-param>
<param-name>javax.faces.VALIDATE_EMPTY_FIELDS</param-name>
<param-value>true</param-value>
</context-param>
....
向 JSF 注册验证器(faces-config.xml):
<validator>
<validator-id>RequiredValidator</validator-id>
<validator-class>cz.test.RequiredValidator</validator-class>
</validator>
以及使用所需验证器和 TesterBean2 (index.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>
<title>Testing page</title>
</h:head>
<h:body>
<h:form>
<h:messages/>
<h:inputText id="fieldValue"
title="#testerBean2.someValue"
value="#testerBean2.someValue"
style="#empty testerBean2.someValue ? 'background-color: yellow;' : ''">
<f:validator validatorId="RequiredValidator"/>
<f:attribute name="message" value="Field is mandatory"/>
</h:inputText>
<h:commandButton value="Store" action="#testerBean2.storeValue"/>
<h:commandButton value="Erase" action="#testerBean2.eraseValue"/>
</h:form>
</h:body>
</html>
注意:在 h:inputText 中不能使用必需的属性。它会超过所需的验证器。
【讨论】:
以上是关于JSF2 - 为啥渲染响应不重新渲染组件设置?的主要内容,如果未能解决你的问题,请参考以下文章
为啥组件在状态更改后不重新渲染。在 react-native 函数组件中