Значение параметра "Ошибка преобразования" для "нулевого конвертера". Зачем мне нужен конвертер в JSF?
У меня проблемы с пониманием того, как эффективно использовать выделение в JSF 2 с POJO/ сущностью. Например, я пытаюсь выбрать Warehouse
сущность через раскрывающийся список:
<h:selectOneMenu value="#{bean.selectedWarehouse}">
<f:selectItem itemLabel="Choose one .." itemValue="#{null}" />
<f:selectItems value="#{bean.availableWarehouses}" />
</h:selectOneMenu>
И ниже управляемый боб:
@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
,
Когда я отправляю форму, это терпит неудачу со следующим сообщением лиц:
Значение параметра Ошибка конверсии com.example.Warehouse@cafebabe для нулевого конвертера.
Я надеялся, что JSF сможет просто установить правильный Warehouse
возражать против моего управляемого боба, когда я обернуть его в SelectItem
, Оборачивая мою сущность в SelectItem
должен был пропустить создание Converter
для моей сущности.
Я действительно должен использовать Converter
всякий раз, когда я хочу использовать сущности в моем <h:selectOneMenu>
? Для JSF должна быть возможность просто извлечь выбранный элемент из списка доступных элементов. Если мне действительно нужно использовать конвертер, каков практический способ сделать это? До сих пор я дошел до этого:
- Создать
Converter
реализация для субъекта. - Переопределение
getAsString()
, Я думаю, что мне это не нужно, так как свойство labelSelectItem
будет использоваться для отображения метки выпадающего списка. - Переопределение
getAsObject()
, Я думаю, что это будет использоваться, чтобы вернуть правильныйSelectItem
или объект в зависимости от типа выбранного поля, определенного в управляемом компоненте.
getAsObject()
смущает меня Каков эффективный способ сделать это? Имея строковое значение, как я могу получить связанный объект сущности? Должен ли я запросить объект объекта из сервисного объекта на основе строкового значения и вернуть объект? Или, возможно, каким-то образом я могу получить доступ к списку сущностей, которые формируют элементы выбора, зациклить их, чтобы найти правильную сущность, и вернуть сущность?
Каков нормальный подход к этому?
3 ответа
Вступление
JSF генерирует HTML. HTML в терминах Java в основном один большой String
, Чтобы представить объекты Java в HTML, они должны быть преобразованы в String
, Кроме того, при отправке формы HTML отправленные значения обрабатываются как String
в параметрах HTTP-запроса. Под прикрытием JSF извлекает их из HttpServletRequest#getParameter()
который возвращается String
,
Преобразовать между нестандартным объектом Java (т.е. не String
, Number
или же Boolean
для которого EL имеет встроенные преобразования, или Date
для которого JSF предоставляет встроенные <f:convertDateTime>
тег), вы действительно должны предоставить Converter
, SelectItem
не имеет никакого специального назначения вообще. Это просто остаток от JSF 1.x, когда было невозможно поставить, например, List<Warehouse>
прямо к <f:selectItems>
, Он также не имеет особого отношения к этикеткам и конверсии.
getAsString ()
Вам необходимо реализовать getAsString()
метод таким образом, чтобы желаемый объект Java был представлен в уникальном String
представление, которое можно использовать в качестве параметра HTTP-запроса. Обычно здесь используется технический идентификатор (первичный ключ базы данных).
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
if (modelValue == null) {
return "";
}
if (modelValue instanceof Warehouse) {
return String.valueOf(((Warehouse) modelValue).getId());
} else {
throw new ConverterException(new FacesMessage(modelValue + " is not a valid Warehouse"));
}
}
Обратите внимание, что возвращение пустой строки в случае нулевого / пустого значения модели является значительным и требуется для javadoc. См. Также Использование "Пожалуйста, выберите" f:selectItem с нулевым / пустым значением внутри ap: selectOneMenu.
getAsObject ()
Вам необходимо реализовать getAsObject()
таким образом, что именно это String
представление, как возвращено getAsString()
может быть преобразован обратно в точно такой же объект Java, указанный как modelValue
в getAsString()
,
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()
в бесконечном цикле.
использование
Наконец, просто аннотируйте Converter
с @FacesConverter
чтобы подключить рассматриваемый тип объекта, JSF автоматически позаботится о преобразовании, когда Warehouse
Тип когда-либо входит в картину:
@FacesConverter(forClass=Warehouse.class)
Это был "канонический" подход JSF. В конце концов, это не очень эффективно, так как он мог бы просто взять предмет из <f:selectItems>
, Но самый важный момент Converter
является то, что он возвращает уникальный String
представление, так что объект Java может быть идентифицирован простым String
подходит для передачи по HTTP и HTML.
Универсальный конвертер на основе toString()
Утилита JSF OmniFaces имеет SelectItemsConverter
который работает на основе toString()
результат сущности. Таким образом, вам не нужно возиться с getAsObject()
и более дорогие операции с базами данных. Для некоторых конкретных примеров использования смотрите также витрину.
Чтобы использовать это, просто зарегистрируйте это как ниже:
<h:selectOneMenu ... converter="omnifaces.SelectItemsConverter">
И убедитесь, что toString()
вашей Warehouse
Сущность возвращает уникальное представление сущности. Например, вы можете напрямую вернуть идентификатор:
@Override
public String toString() {
return String.valueOf(id);
}
Или что-то более читаемое / многоразовое:
@Override
public String toString() {
return "Warehouse[id=" + id + "]";
}
Смотрите также:
- Как заполнить опции h:selectOneMenu из базы данных?
- Универсальный конвертер сущностей JSF - так что вам не нужно писать конвертер для каждой сущности.
- Использование перечислений в элементах выбора JSF - перечисления нужно обрабатывать немного по-другому
- Как добавить @EJB, @PersistenceContext, @Inject, @Autowired и т. Д. В @FacesConverter?
Не имеет отношения к проблеме, поскольку в JSF 2.0 больше не требуется явно иметь List<SelectItem>
как <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;
Пример универсального конвертера JSF с ABaseEntity и идентификатором:
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>
Я добился этого, просто изменив тип «значения» в <h:selectOneMenu.. на String.