Java JSF2-Bean验证:验证失败->;空值将替换为托管bean中的最后一个有效值

Java JSF2-Bean验证:验证失败->;空值将替换为托管bean中的最后一个有效值,java,validation,jsf-2,Java,Validation,Jsf 2,我不理解JSF2在valdation期间的行为。希望有人能帮助我 我有一个表单,其中字段在(ajax)提交后进行验证-确定 如果验证失败,将显示一条错误消息-确定 例如,当我输入一个有效的生日,并且字段名为空时,提交后会显示一条关于姓名的错误消息。 现在,当我输入一个有效的名字并从生日字段中删除输入时,会显示一条关于生日的错误消息(没关系),但现在旧的“有效”生日也出现在输入字段中 我怎样才能避免这种行为? 当我提交一个空字段时,我希望看到一条错误消息和一个空字段 以下是我的示例代码: 我使用一


我不理解JSF2在valdation期间的行为。希望有人能帮助我

我有一个表单,其中字段在(ajax)提交后进行验证-确定
如果验证失败,将显示一条错误消息-确定

例如,当我输入一个有效的生日,并且字段名为空时,提交后会显示一条关于姓名的错误消息。
现在,当我输入一个有效的名字并从生日字段中删除输入时,会显示一条关于生日的错误消息(没关系),但现在旧的“有效”生日也出现在输入字段中

我怎样才能避免这种行为? 当我提交一个空字段时,我希望看到一条错误消息和一个空字段

以下是我的示例代码:

我使用一个包含EntityBean(Contact)的ManagedBean(TestBean)。联系人包含每个注释的验证

public class Contact implements Serializable {
    @NotNull 
    @Temporal(TemporalType.DATE)
    private Date birthday;

    @NotNull 
    @Size(min=3, max=15)
    private String name;

    //...
}
我的ManagedBean:

下面是我的JSF页面:

<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:body>
    <h:form>     
        <h:panelGrid columns="3">   

        <h:outputText value="Birthday: " />  
        <h:inputText id="birthday" value="#{testBean.contact.birthday}">
            <f:convertDateTime/>
        </h:inputText>  
        <h:message for="birthday" />  

        <h:outputText value="Name: " />  
        <h:inputText id="name" value="#{testBean.contact.name}"/>
        <h:message for="name" />

        </h:panelGrid>  

        <h:commandButton value="submit" action="#{testBean.save}"> 
            <f:ajax execute="@form" render="@form"/>
        </h:commandButton> 

        <h:commandButton value="newContact" actionListener="#{testBean.newContact}"
                         immediate="true"> 
            <f:ajax render="@form"/>
        </h:commandButton> 

    </h:form>
</h:body>
</html>

最后是web.xml中的一个片段

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

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

javax.faces.explait_EMPTY_STRING_SUBMITTED_VALUES_为_NULL
真的
javax.faces.VALIDATE_空_字段
真的

感谢您提供的一些提示

我认为在正常使用期间,人们不会输入有效的日期、提交,然后在再次提交之前删除该日期。我知道您在测试期间发现了这一点,但可能人们只是试图成功地填写表单,而不是删除他们已经输入的内容,在这种情况下,保留最后一个有效值可能是最好的功能

如果你坚持。。。似乎从未调用setter方法“生日”,因为该值无效,然后当重新显示页面时,将显示“生日”的当前值(当前值是以前保存的有效值)。也许您可以编写一个自定义验证器来设置值,然后验证值,但这并没有多大意义。对于用户输入“昨天”之类的文本字符串而不是有效日期的情况,您仍然必须首先验证该值,然后必须根据该无效值将日期设置为某个值,然后必须将消息添加到FacesContext。因此,代码必须执行以下操作。

1) 验证日期值
2) 如果无效,则将字段设置为有意义的值,并向FacesContext添加错误消息。
3) 如果它是有效的,那么就使用它


这是可以做到的,但很奇怪,因为您正在更改字段的值,即使传入的值无效…

Aaron已经描述了该行为

通过单击“新建联系人”按钮,我描述的问题也存在。如果第一次提交无效(输入生日,名称字段为空),将显示错误消息。嗯

之后,“newContact”按钮不会刷新(清除)视图。尽管模型已重置(contact=newcontact())

我在这里找到了一些提示:

以下是我的解决方案:

public void newContact(ActionEvent ae) {
    contact = new Contact();
    contact.setBirthday(new Date()); //for testing only

    resetForm(ae.getComponent());
}

private void resetForm(UIComponent uiComponent) {
    //get form component
    UIComponent parentComponent = uiComponent.getParent();
    if (uiComponent instanceof UIForm)
        resetFields(uiComponent);
    else if (parentComponent != null)
        resetForm(parentComponent);
    else
        resetFields(uiComponent);

}

private void resetFields(UIComponent baseComponent) {
    for (UIComponent c : baseComponent.getChildren()) {
        if (c.getChildCount() > 0)
            resetFields(c);

        if (c instanceof UIInput)
            ((UIInput) c).resetValue();
    }
}

你的特殊问题是由


javax.faces.explait_EMPTY_STRING_SUBMITTED_VALUES_为_NULL
真的
以及Mojarra的
HtmlBasicRenderer#getCurrentValue()
中的一个bug(至少是一个疏忽):

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;
通常,当
UIInput
组件成功转换和验证时,提交的值设置为
null
。当JSF即将重新显示该值时,它首先检查提交的值是否不是
null
,然后再继续重新显示模型值。但是,使用此上下文参数时,当无效时,它是
null
,而不是空字符串,因此当您删除所需字段的初始值时,它将始终重新显示原始模型值

要测试它,请将该上下文参数值设置为
false
,或将其全部删除。您将看到它按预期工作。但是,它会带来一个缺点,即您的模型值将在空但非必需的字段上被空字符串弄乱,并且您将失去使用JSR303bean验证的
@NotNull
注释的优势

要解决此问题,您必须修改
HtmlBasicRenderer#getCurrentValue()
的第一部分,如下所示:

if (component instanceof UIInput && !((UIInput) component).isValid()) {
    Object submittedValue = ((UIInput) component).getSubmittedValue();
    if (submittedValue != null) {
        // value may not be a String...
        return submittedValue.toString();
    } else {
        return null;
    }
}

我已经向Mojarra guys报告过了。

有一个类似的问题,当字段被清空并且另一个组件验证失败时,从支持bean加载的值将被重置。我必须对BalusC的代码稍加修改才能使其正常工作

protected String getCurrentValue(FacesContext context,
                                     UIComponent component) {

        if (component instanceof UIInput && !((UIInput) component).isValid()) {
            Object submittedValue = ((UIInput) component).getSubmittedValue();
            if (submittedValue != null) {
                // value may not be a String...
                return submittedValue.toString();
            } else {
                return null;
            }
        }


        String currentValue = null;
        Object currentObj;

        if ( component instanceof UIInput && ((UIInput)component).isLocalValueSet() )
        {
           currentObj = ((UIInput)component).getLocalValue();
        }
        else {
            currentObj = getValue(component);
        }

        if (currentObj != null) {
            currentValue = getFormattedValue(context, component, currentObj);
        }
        return currentValue;


    }

嗨,亚伦,谢谢你的回答!在我的示例代码中,还有一个案例在尝试替换后端模型(button newContact)时显示了相同的问题。对我来说,这看起来像是一个现实世界的问题,而不是一个学术测试案例。我找到了一个解决方案(不太好,但很有效…)。对于解决方案,我稍微编辑了示例代码。查看我自己的答案我注意到,当输入通过转换器绑定到非字符串属性时,可能由于相同的根本原因,会出现类似的行为。当您输入一个空字符串并且验证由于不同的字段而失败时,您的(有效)空字符串将被bean中的值覆盖。被报道为@BalusC:谢谢你的详细解释!你在这里的解决方案/解释常常让我感到很高兴;-)@BalusC这是否意味着他编译了JSF的自定义版本?我也遇到过同样的问题,希望这不是唯一的解决办法。@Mike:对于初学者来说,最简单的方法是将
HtmlBasicRenderer
的原始
.java
源文件直接复制粘贴到你的webapp项目中(同时保留原始包!),然后编辑它。它将在类加载中优先于
protected String getCurrentValue(FacesContext context,
                                     UIComponent component) {

        if (component instanceof UIInput && !((UIInput) component).isValid()) {
            Object submittedValue = ((UIInput) component).getSubmittedValue();
            if (submittedValue != null) {
                // value may not be a String...
                return submittedValue.toString();
            } else {
                return null;
            }
        }


        String currentValue = null;
        Object currentObj;

        if ( component instanceof UIInput && ((UIInput)component).isLocalValueSet() )
        {
           currentObj = ((UIInput)component).getLocalValue();
        }
        else {
            currentObj = getValue(component);
        }

        if (currentObj != null) {
            currentValue = getFormattedValue(context, component, currentObj);
        }
        return currentValue;


    }