null

Почему ошибка валидации в JSF блокирует обновление инпутов?

Достаточно часто, при разработке программисты сталкиваются с ошибками, причины которых не сразу удаётся определить. Сегодня рассмотрим одну из таких ошибок, возникающую в JSF приложениях.

Итак, о какой проблеме пойдет речь?

В качестве описания ошибки приведу следующее summary: "После проваленной валидации значения input'ов не синхронизируют своё состояние с полями bean'ов невзирая на указанный update".

Как, где и когда возникла данная ошибка?

В одном из веб-приложений, построенном на JSF интерфейс представлял из себя страницу с табами (tabView из библиотеки компонентов primefaces). Один из табов (вкладок) содержал таблицу с основными полями сущности. Он использовался для поиска элементов, контроля работ и т.п.

Также была возможность перейти к просмотру и созданию конкретного элемента. Данные действия происходили в новом табе. Его интерфейс был достаточно жирным, содержал множество различной информации и сложных компонентов.

Сама проблема заключалось в следующем:

Вторая вкладка, имела набор обязательных для заполнения полей, помеченных атрибутом required. При попытке submit'а формы с невалидными данными, пользователю показывался специальный блок произошедших ошибок, а поля и label'ы приобретали внешнее оформление, показывающее наличие ошибок. По сути, это типовая форма с валидацией.

Но вот в чём оказалась проблема:

Пользователь, не пройдя валидацию, решает не заполнять "плохие" поля и не доделывать работу. Возвращается к другому табу (со списком), а оттуда переходит назад в исходный интерфейс (с которым только что работал). Возможно, он решил создать всё заново или ещё что-нибудь (всё что может пользователю придти в голову).

В этот момент и возникает ошибка:

Повторно перейдя на этот таб, оказывается, что в полях ввода остались данные из предыдущей сущности, а обрамление полей и подписей к ним стилизованны под ошибку валидации.

Кто виноват и в каком месте проблемный участок кода?

Так как интерфейс был наполнен компонентами и различной логикой, сходу понять в чём проблема не вышло.

Первым кандидатом в виновники был несработавший обработчик, отвечающий за изменение сущности в managed bean'е к которой были привязаны не обновившие значения поля ввода.Пачка отладочного вывода и последующий мониторинг лога после каждого клика показал, что обработчики вызываются корректно, данные обновляются и всё должно быть хорошо.

Вторым предположением стал, возможно, неправильно заданный атрибут update, который просто не включал нужные элементы представления на перерисовку при формировании ответа. Оно также не подтвердилось.

Если всё правильно, то почему это происходит?

Если умозаключения и анализ проблемы привели к тому, что ошибка возникает вследствие магического явления, то стоит пересмотреть знания предметной области и провести тщательное изучение используемых средств. В данной ситуации проблема кроется внутри JSF'а.

Чтобы разобраться в работе framework'а и выявить проблему, необходимо уменьшитьь окружение и оборудовать наиболее простой полигон для воспроизведения ошибки. Удаление лишних деталей, также позволит сделать объяснение более наглядным.

Код страницы:

<h:form  id="form">
        <p:outputLabel value="Required string:" for="str"/>
        <p:inputText id="str" required="true" value="#{indexBean.text2}"/>

        <hr/>${indexBean.i}<br/>

        <p:outputLabel value="I:" for="i"/>
        <p:inputText id="i" value="#{indexBean.i}"/>

        <p:commandLink action="#{indexBean.something()}" ajax="true"
                       value="++" process="@this" update="@form"/>

        <br/>
        <p:commandButton  value="Submit" ajax="true" update="@form"/>
</h:form>


Код бина:

@ManagedBean
@ViewScoped
public class IndexBean implements Serializable{

   private String text2;
   private int i;

  @PostConstruct
   public void init(){ i = 0; }
    
   public void something(){
        i++;
   }
}


На полученном коде можно тестировать ошибку. Нажатие на commandLink вызывает увеличение переменной в бине на единицу, после чего происходит перерисовка формы и обновление значения как в поле ввода, так и строкой выше в обычном el выражении. Если заполнить текстовое поле "str" и нажать на кнопку "submit", то нажатие на commandLink также будет работать.

Если же оставить обязательное поле незаполненным и попытаться сделать "submit" формы, то произойдет ошибка валидации. Последующие нажатия на инкремент, буду перерисовывать только значения обычного текста, но не значение внутри input'а.


Чтобы разобраться в этой проблеме необходимо посмотреть на то, как устроен жизненный цикл JSF.

http://developersbook.com/jsf/images/JSF-Lifecycle.png

Из диаграммы видно, что в момент отправки формы с незаполненным обязательным полем на стадии валидации происходит ошибка. JSF пропускает фазы синхронизации значений компонента с Bean'ом и выполнения методов приложения.

Это приводит к тому, что происходит рассинхронизация между значением во внутреннем представлении компонента и полем bean'а к которому оно привязано.

Осталось понять почему не происходит синхронизаций компонента и полей bean'а в момент последующих запросов, в которых явно указан атрибут update с перечисленными компонентами.

Истина кроется в логике работы update:

Атрибут 'update' - лишь сообщает JSF'у, что необходимо включить в response. Компоненты, которые передаются в update не обновляют своих значений. Для этого служит атрибут 'process'.

Как решить проблему?

Есть несколько вариантов решения проблемы:

  1. Включение компонента в process, но это никак не решает ситуацию при которой эта ошибка возникла.
  2. Разделение интерфейса на разные формы. И вызов update полей из другой формы. Ошибку с вкладками это решает, но такой подход не всегда возможен.
  3. Использование атрибута resetValues. Он присутствует у таких компонетов primefaces как <p:ajax />, <p:commandButton />, <p:commandLink /> и появился в версии 4.0. Также в JSF-2.2 этот атрибут появился у <f:ajax>. С помощью resetValues, можно принудительно сбросить значения компонентов и получить корректные значения из bean'а.

На этом я сегодня с вами прощаюсь. Удачи в разработке!