41

我在理解如何在 JSF 2 中有效地使用 POJO/entity 选择时遇到问题。例如,我正在尝试Warehouse通过以下下拉列表选择一个实体:

<h:selectOneMenu value="#{bean.selectedWarehouse}">
    <f:selectItem itemLabel="Choose one .." itemValue="#{null}" />
    <f:selectItems value="#{bean.availableWarehouses}" />
</h:selectOneMenu>

以及下面的托管 bean:

@Named
@ViewScoped
public class Bean {

    private Warehouse selectedWarehouse;
    private List<SelectItem> availableWarehouses;

    // ...

    @PostConstruct
    public void init() {
        // ...

        availableWarehouses = new ArrayList<>();

        for (Warehouse warehouse : warehouseService.listAll()) {
            availableWarehouses.add(new SelectItem(warehouse, warehouse.getName()));
        }
    }

    // ...
}

请注意,我使用整个Warehouse实体作为 的值SelectItem

当我提交表单时,失败并显示以下面孔消息:

'null Converter' 的转换错误设置值'com.example.Warehouse@cafebabe'。

当我将 JSFWarehouse包装在一个SelectItem. 将我的实体包装在里面SelectItem是为了跳过Converter为我的实体创建一个。

Converter每当我想使用我的实体时,我真的必须使用<h:selectOneMenu>吗?对于 JSF,应该可以只从可用项目列表中提取所选项目。如果我真的必须使用转换器,那么实际的做法是什么?到目前为止,我想到了这个:

  1. 为实体创建一个Converter实现。
  2. 覆盖getAsString(). 我想我不需要这个,因为 的 label 属性SelectItem将用于显示下拉选项标签。
  3. 覆盖getAsObject(). 我认为这将用于SelectItem根据托管 bean 中定义的所选字段的类型返回正确的或实体。

getAsObject()我很困惑。这样做的有效方法是什么?有了字符串值,我如何获取关联的实体对象?我应该根据字符串值从服务对象中查询实体对象并返回实体吗?或者也许我可以以某种方式访问​​构成选择项的实体列表,循环它们以找到正确的实体,然后返回实体?

这是什么正常的做法?

4

2 回答 2

78

介绍

JSF 生成 HTML。HTML 在 Java 术语中基本上是一大String. 要在 HTML 中表示 Java 对象,必须将它们转换为String. 此外,当提交 HTML 表单时,提交的值将被视为StringHTTP 请求参数中的值。在幕后,JSF 从HttpServletRequest#getParameter()which return 中提取它们String

要在非标准 Java 对象之间进行转换(即不是StringNumber或者BooleanEL 具有内置转换,或者Date/ LocalDate/ ZonedDateTimeJSF 为其提供内置<f:convertDateTime>标记),您确实必须提供自定义的Converter. SelectItem根本没有特殊用途。当无法List<Warehouse>直接提供例如<f:selectItems>. 它对标签和转换也没有特殊处理。

getAsString()

您需要以getAsString()这样的方式实现方法,即所需的 Java 对象以唯一的 String 表示形式表示,该表示可以用作 HTTP 请求参数。通常,此处使用技术 ID(数据库主键)。

public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
    if (modelValue == null) {
        return ""; // Never return null here!
    }

    if (modelValue instanceof Warehouse) {
        return String.valueOf(((Warehouse) modelValue).getId());
    } else {
        throw new ConverterException(new FacesMessage(modelValue + " is not a valid Warehouse"));
    }
}

请注意,在 null/空模型值的情况下返回空字符串很重要,并且javadoc需要:

返回:如果值为 null,则返回零长度字符串,否则为转换结果

否则生成的<option>将没有value属性,默认情况下将项目标签发送回getAsObject(). 另请参阅在 ap:selectOneMenu 中使用具有空值/空值的“请选择” f:selectItem

获取对象()

您需要以getAsObject()这样的方式实现,即可以将由返回的表示完全转换回与中指定的完全相同的Java 对象。StringgetAsString()modelValuegetAsString()

public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
    if (submittedValue == null || submittedValue.isEmpty()) {
        return null;
    }

    try {
        return warehouseService.find(Long.valueOf(submittedValue));
    } catch (NumberFormatException e) {
        throw new ConverterException(new FacesMessage(submittedValue + " is not a valid Warehouse ID"), e);
    }
}

换句话说,您必须在技术上能够将返回的对象作为modelValue参数getAsString()传回,然后在无限循环中将获得的字符串作为submittedValue参数传回。getAsObject()

用法

最后只需注释Converterwith@FacesConverter以挂钩有问题的对象类型,JSF 将在Warehouse类型出现时自动处理转换:

@FacesConverter(forClass=Warehouse.class)

那是“规范的”JSF 方法。毕竟它不是很有效,因为它确实也可能只是从<f:selectItems>. 但是a最重要的一点Converter是它返回一个唯一的 String表示,这样Java对象就可以被一个String适合在HTTP和HTML中传递的简单标识。

基于 toString() 的通用转换器

JSF 实用程序库OmniFaces有一个SelectItemsConverter基于toString()实体结果的工作。这样您就不再需要摆弄getAsObject()昂贵的业务/数据库操作了。有关一些具体的使用示例,另请参见展示

要使用它,只需将其注册如下:

<h:selectOneMenu ... converter="omnifaces.SelectItemsConverter">

并确保toString()您的Warehouse实体返回实体的唯一表示。例如,您可以直接返回 ID:

@Override
public String toString() {
    return String.valueOf(id);
}

或者更具可读性/可重用性的东西:

@Override
public String toString() {
    return "Warehouse[id=" + id + "]";
}

也可以看看:


与问题无关,因为 JSF 2.0 不再明确要求具有List<SelectItem>as<f:selectItem>值。一个List<Warehouse>也足够了。

<h:selectOneMenu value="#{bean.selectedWarehouse}">
    <f:selectItem itemLabel="Choose one .." itemValue="#{null}" />
    <f:selectItems value="#{bean.availableWarehouses}" var="warehouse"
        itemLabel="#{warehouse.name}" itemValue="#{warehouse}" />
</h:selectOneMenu>
private Warehouse selectedWarehouse;
private List<Warehouse> availableWarehouses;
于 2011-01-19T12:39:11.217 回答
4

带有 ABaseEntity 和标识符的 JSF 通用转换器示例:

ABaseEntity.java

public abstract class ABaseEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    public abstract Long getIdentifier();
}

SelectItemToEntityConverter.java

@FacesConverter(value = "SelectItemToEntityConverter")
public class SelectItemToEntityConverter implements Converter {

    @Override
    public Object getAsObject(FacesContext ctx, UIComponent comp, String value) {
        Object o = null;
        if (!(value == null || value.isEmpty())) {
            o = this.getSelectedItemAsEntity(comp, value);
        }
        return o;
    }

    @Override
    public String getAsString(FacesContext ctx, UIComponent comp, Object value) {
        String s = "";
        if (value != null) {
            s = ((ABaseEntity) value).getIdentifier().toString();
        }
        return s;
    }

    private ABaseEntity getSelectedItemAsEntity(UIComponent comp, String value) {
        ABaseEntity item = null;

        List<ABaseEntity> selectItems = null;
        for (UIComponent uic : comp.getChildren()) {
            if (uic instanceof UISelectItems) {
                Long itemId = Long.valueOf(value);
                selectItems = (List<ABaseEntity>) ((UISelectItems) uic).getValue();

                if (itemId != null && selectItems != null && !selectItems.isEmpty()) {
                    Predicate<ABaseEntity> predicate = i -> i.getIdentifier().equals(itemId);
                    item = selectItems.stream().filter(predicate).findFirst().orElse(null);
                }
            }
        }

        return item;
    }
}

和用法:

<p:selectOneMenu id="somItems" value="#{exampleBean.selectedItem}" converter="SelectItemToEntityConverter">
    <f:selectItem itemLabel="< select item >" itemValue="#{null}"/>
    <f:selectItems value="#{exampleBean.availableItems}" var="item" itemLabel="${item.identifier}" itemValue="#{item}"/>
</p:selectOneMenu>
于 2017-08-02T16:49:30.070 回答