使用 JSF 转换器输出时用指定值替换 null 或空字符串

Replace null or empty strings with a specified value while outputting using a JSF converter

以下是一个转换器,主要用于 trim 前导和尾随白色 space 并用单个 [= 替换句子或文本中单词之间的多个 space 69=]。转换器现已修改为用 "Not available" 替换 null 或空字符串(如果需要,可以动态本地化)。

@FacesConverter(forClass = String.class)
public class StringTrimmer implements Converter {

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        return Boolean.TRUE.equals(component.getAttributes().get("skipConverter")) ? value : value == null ? null : value.trim().replaceAll("\s+", " ");
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        return Boolean.TRUE.equals(component.getAttributes().get("skipConverter")) ? value == null ? null : value.toString() : value == null || ((String) value).trim().length() == 0 ? "Not available" : ((String) value).trim().replaceAll("\s+", " ");
    }
}

由于未调用转换器,当模型值为 null 基于 the previous question 时,已扩展 com.sun.faces.renderkit.html_basic.TextRenderer 以调用转换器,当 属性 关联模型中的值为 null.

public final class HtmlBasicRenderer extends TextRenderer {

    @Override
    public String getCurrentValue(FacesContext context, UIComponent component) {

        if (component instanceof UIInput) {
            Object submittedValue = ((UIInput) component).getSubmittedValue();

            if (submittedValue != null) {
                return submittedValue.toString();
            }
        }

        return getFormattedValue(context, component, getValue(component));
    }
}

已删除 following 条件测试,以便可以调用 getFormattedValue() 方法,即使遇到 null 值。

Object currentObj = getValue(component);

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

已在 faces-config.xml 中注册如下。

<render-kit>
    <renderer>
        <component-family>javax.faces.Output</component-family>
        <renderer-type>javax.faces.Text</renderer-type>
        <renderer-class>com.example.renderer.HtmlBasicRenderer</renderer-class>
    </renderer>
</render-kit>

转换器 StringTrimmer 仍未调用 (getAsString()),当目标模型中的 属性 值 returns null.

在 EL 中像 #{empty bean.value ? 'Not available' : bean.value} 一样在整个应用程序的任何地方进行条件测试是疯狂的。有什么建议吗?

这是 Mojarra 2.2.12。


更新:

getFormattedValue() 方法中的 return 语句之一 return 空字符串 "" 时,当 currentValuenull,修改为return调用

的转换值
javax.​faces.​convert.​Converter.getAsString(FacesContext context, UIComponent component, Object value)

在该方法中 getFormattedValue()

因此,following

if(currentValue == null) {
    return "";
}

需要替换为,

if (currentValue == null) {
    converter = Util.getConverterForClass("".getClass(), context);
    return converter == null ? "" : converter.getAsString(context, component, currentValue);
}

(需要建议)。

首先,Converter 绝不会强制执行 "default value"。

不管是什么问题,无论你在getAsString()中做什么,你都应该保证结果String在你传回来的时候能转换回原来的Object getAsObject()。您的转换器不会那样做。即使您不太可能使用它,从技术上讲,也需要修改转换器以将确切的字符串 "Not available" 转换回 null。换句话说,您的转换器必须设计成 getAsObject()getAsString() 可以在无限循环中成功传递彼此的结果,并且每次都返回相同的结果。

关于强制执行默认值的具体功能要求,而不是在 Converter 中,您应该在模型或视图中执行此操作,具体取决于实际默认值的来源。在您的特定情况下,当没有这样的值时,您只想在用户界面中有一个默认占位符 text/label 。这属于视图。

Putting a conditional test in EL like #{empty bean.value ? 'Not available' : bean.value} everywhere throughout the application is insanity.

我完全同意,如果您的应用程序开发已经进行到一半并且到处都有数百个应用程序,那么这就是疯狂。然而,如果您从一开始就已经考虑到这一点,那就不是精神错乱。如果你没有,那么好吧,吸取教训,咬紧牙关并定期相应地修复代码。体面的 IDE 具有基于正则表达式的查找和替换,可以帮助解决这个问题。每个人都去过那里,做过那种疯狂的事,甚至我自己。要减少样板文件,请将其包装在 EL 函数或标记文件中。

至于模型值为null时未调用Converter的具体问题,我个人完全同意是意外行为,曾被报告为Mojarra issue 630. This is later closed as WONTFIX because it caused test case failures and is after all better to be reported as a JSF specification issue. This was done as JSF spec issue 475(已经在 J​​SF 1.2 期间)。我对此检查了 MyFaces 2.2.9,它也没有触发转换器,因此暴露了相同的规范问题。

但是,技术问题是可以理解的。 null 值没有合理的 getClass(),因此无法通过值的 class 以这种方式查找转换器。它仅在转换器显式注册到组件上时才起作用。

<h:outputText value="#{bean.potentiallyNullValue}" converter="stringTrimmer" />

当值是一个空字符串 "" 时它应该工作,这在扩展 Mojarra TextRenderer 的自定义渲染器的 getValue() 中实现起来相对简单。

@Override
protected Object getValue(UIComponent component) {
    Object value = super.getValue(component);
    return (value != null) ? value : "";
}

然而,当我在这里自己尝试时,它仍然失败了。结果是,当值是 String 的一个实例时,转换器 by-class 被完全跳过。仅当转换器在组件上显式注册时,它仍然有效。这很可能是在为 JSF 1.2 实现 spec issue 131 期间的疏忽(在此版本之前,根本不支持 String.class 的转换器,并且此问题仅针对解码而不是编码修复)。

这可以在相同的自定义渲染器中被覆盖(使用上面的 getValue() 覆盖),下面覆盖 getFormattedValue(),从而显式查找转换器。

@Override
protected String getFormattedValue(FacesContext context, UIComponent component, Object currentValue) throws ConverterException {
    Converter converter = ((UIOutput) component).getConverter();

    if (converter == null) {
        converter = context.getApplication().createConverter(currentValue.getClass());
    }

    return super.getFormattedValue(context, component, "".equals(currentValue) ? null : currentValue, converter);
}

请注意,您不需要检查 UIInput,因为您已经在 javax.faces.Output/javax.faces.Text 组件 family/type 上明确注册了您的自定义渲染器(即它只会在 <h:outputText> 个组件上 运行。

尽管如此,最好的解决方案仍然是为此创建一个 EL 函数或标记文件。

<h:outputText value="#{empty bean.value ? 'Not available' : bean.value}" />
<h:outputText value="#{of:coalesce(bean.value, 'Not Available')}" />
<h:outputText value="#{of:coalesce(bean.value, i18n.na)}" />
<h:outputText value="#{your:value(bean.value)}" />
<your:text value="#{bean.value}" />