Нажатие commandButton в p: dataTable приводит к тому, что bean-компонент @ViewScoped будет повторно @Produced после вызова приложения

Вид:

<h:form ...
  <p:dataTable value="#{myBean.list}" var="data" ...
     <p:column ...
        <h:commandButton action="#{controller.method(data.id)}" />
     </p:column>
  </p:dataTable>
</h:form>

Контроллер:

@ApplicationScoped
public class Controller {
   public String method(final Long dataId) {
        /* Do business */
        return URL_WITH_REDIRECT;
   }
}

Производитель

(с использованием аннотации @ViewScoped CDI, как описано здесь)

@ApplicationScoped
public class Producer {
   @Named @ViewScoped @Producer
   public MyBean getMyBean() {
        final MyBean bean = new MyBean();
        bean.list = new ArrayList<Data>(); // where Data has a Long id field
        /* Do business and populate list */
        return bean;
   }
}

Проблема и ее сценарий

  1. GET the page
    1. The bean is produced
    2. Представление визуализируется
    3. Ответ отправлен в браузер
  2. Click the button
    1. Data is POSTed to server
    2. Этапы 1– 4 выполняются без каких-либо проблем и используют @ViewScoped bean-компоненты, как и ожидалось.
    3. Фаза 5: controller.method вызывается с data.id и обращается к bean-компонентам, сгенерированным в 1.1.
    4. Метод возвращает перенаправление String
    5. <сильный>!! Продюсер вызывается снова!! — мы все еще находимся в фазе APPLICATION_INVOCATION, но уже после фактического вызова метода
  3. Браузер получает перенаправление
  4. GET следующая страница...

Полуослиное решение, которое работает:

Короче говоря: при щелчке скопируйте идентификатор за пределы таблицы данных и нажмите кнопку отправки.

На h:commandButton внутри столбца таблицы добавлено:

onclick="$('input[id*=selectedDataId]').val('#{data.id}'); $('button[id*=callMethod]').trigger('click');"

Вне стола:

<h:inputHidden id="{selectedDataId}"binding="#{selectedDataId}"/>
<p:commandButton type="submit"
                 id="callMethod"
                 label="Hidden button"
                 action="#{controller.method(selectedDataId.value)}"/>

В конце концов это работает, но я не смог понять, что заставляет первый и базовый подход повторно инициализировать компонент с областью видимости. Глядя на трассировку стека (см. ниже), кажется, что он перестраивает строки.

Вопрос:

Есть ли у кого-нибудь объяснение и, возможно, предостережения, на которые следует обратить внимание в отношении этой проблемы?

Трассировки стека

Где: getPipelinecheckSearchResults — это вызов для получения списка, поддерживающего таблицу, что приводит к вызову производителя.

трассировка стека

Что я уже просмотрела:

Я прочитал следующие статьи/вопросы SO, не получив лучшего понимания того, почему вышеуказанное (1-е) решение работает именно так.

Компонент ViewScoped воссоздается каждый раз, когда я нажимаю commandButton в моей таблице данных

Почему обратный вызов @PostConstruct срабатывает каждый раз даже если bean-компонент @ViewScoped? JSF

Как я могу передать выбранную строку в commandLink внутри dataTable?

http://balusc.blogspot.de/2010/06/benefits-and-pitfalls-of-viewscoped.html

https://java.net/jira/browse/JAVASERVERFACES-1492


person Matyas    schedule 27.05.2013    source источник
comment
о, хорошо, я думаю, что недавно наблюдал подобное поведение   -  person Steve Oh    schedule 28.05.2013
comment
Вы пытались вызвать метод myBean вместо controller из командной кнопки?   -  person Steve Oh    schedule 28.05.2013
comment
Вы читали этот stackoverflow.com/questions/ 7675319/   -  person Steve Oh    schedule 28.05.2013
comment
1. Пробовал вызывать метод off из myBean. Та же проблема. (Хотя я бы не стал вкладывать логику в модели, так как они должны быть тупыми). 2. Да, читал, но это относится к actionListener, а в моем случае re@Production после APPLICATION_INVOCATION, поэтому action правильно находит путь   -  person Matyas    schedule 28.05.2013


Ответы (2)


Я нашел несколько источников для jsf/primefaces/ee-api/glassfish и т. д. для отладки поведения, так что вот ответ:

Короче

If a component:

  • Запускает действие (controller.method), которое вызывает перенаправление
  • И помещается в datatable
  • И datatable генерирует свои строки на основе bean-компонента @ViewScoped.

Потом:

  • После вызова controller.method bean-компонент @ViewScoped, от которого зависит datatable, будет регенерирован (со всеми его зависимостями, конечно).

Протестировано: в версии 2.1.7 JSF. Посмотрел в источниках 2.1.19, и я ожидаю там такого же поведения.

Подробности

Для тех, кто громко кричит одинокими летними ночами, спрашивая: "Почему?"

Цепочка «Событий», которые приводят к такому поведению (со ссылками на источники):

  1. Пользователь нажимает кнопку внутри строки таблицы.
  2. Данные POSTed на сервер
  3. Этапы 1-4 проходят по плану
  4. APPLICATION_INVOCATION
    1. The click event is received by JSF. Important: The click event that references the button is wrapped in an event that contains information about the table & the row number the click happened on. For simplicity: rowEvent & clickEvent
    2. Событие «транслируется» в дереве компонентов @ UIViewRoot:794
    3. javax.faces.UIData The grandparent of org.primefaces.component.datatable.DataTable backing p:datatable starts processing the event @ UIData.broadcast(FacesEvent)
      1. The broadcast method first saves the index of the last selected row
      2. Затем он выбирает тот, который указан в rowEvent
      3. Dispatches the clickEvent on the child UIComponent, in our case on the Button
        1. Everything is well & fine, and the event starts getting processed by ActionListener.processAction(ActionEvent)
          1. This in turn invokes controller.method which returns a redirect String and things begin to go downhill
          2. At the end of the method the redirectString is processed by a NavigationHandler
            1. This one seeing that we're about to redirect quickly clears the ViewMap removing all @ViewScoped beans from it at line 179. Which if we think about it is kind of logical, since we're on our way out.
      4. On arriving back in UIData.broadcast which
        • having broadcasted the inner event,
        • не зная, что какое-то внутреннее событие вызвало перенаправление и все, что оно делает, будет выброшено в мусор (из-за 302)
        • в качестве последнего действия пытается выбрать строку, индекс которой был сохранен на шаге 4.3.1
      5. И, конечно же, чтобы выбрать строку, ему нужно знать данные для нее, и именно здесь @ViewScoped bean-компоненты, необходимые для таблицы, регенерируются.

КОНЕЦ

Уведомление

Хотя я не тестировал, я ожидаю такого же поведения h:datatable, p:accordionPanel, p:carousel, p:galleria, p:dataGrid и т. д. Короче говоря, каждый компонент, который является подклассом UIData и не предоставляет redirect - осведомленный метод broadcast.

person Matyas    schedule 29.05.2013
comment
Привет. Здесь та же проблема, но я думаю, что это проблема Primefaces. Если я использую простые теги JSF (я имею в виду h: datatable и т. д.), все работает нормально. Вы нашли какое-нибудь решение? Primefaces даже не работает, если метод действия возвращает значение null (что не приведет к повторному запуску bean-компонента Viewscoped. - person choquero70; 27.11.2013
comment
Более того ... моя проблема заключается в том, что я нажимаю кнопку p: command, чтобы редактировать строку 2-й, 3-й и т. д. страниц данных с простыми лицами. Если я редактирую строку первой строки, все работает нормально, bean-компонент Viewscoped не воссоздается. - person choquero70; 27.11.2013
comment
Добавив информацию журнала в трассировку, я обнаружил, что метод действия не вызывается, когда я нажимаю кнопку p: command на 2-й или следующих страницах таблицы данных. - person choquero70; 27.11.2013

Если я не понимаю это неправильно, вы используете bean-компонент, который каким-то образом привязан к представлению (Seam 3, CODI или ваша собственная пользовательская область, которую вы написали). У вас все в порядке, пока жизненный цикл JSF работает с одним и тем же представлением (что было бы правильным предположением), но вы удивлены, когда вы меняете идентификатор представления, что вы получаете новый экземпляр bean-компонента с областью представления ?? Вся цель области представления состоит в том, чтобы сохранять вещи в одном и том же состоянии представления в JSF, как только вы указываете JSF перейти к другому идентификатору представления, он создает новое состояние представления. Звучит для меня то, что вы на самом деле ищете, это объем разговора.

person LightGuard    schedule 28.05.2013
comment
Используемый @ViewScope — это [описанный здесь][1] [1] verborgh.be/articles/2010/01/06/ - person Matyas; 28.05.2013
comment
С другой стороны: если вы проверите сценарий выше, проблема заключается в том, что производитель вызывается до фактического перенаправления, которое отправляется в браузер (между шагами 2.4 и 3. Таким образом, мы все еще находимся в запросе POST, который исходит от клика и успешно использует @ViewsScoped bean-компоненты, созданные на этапе 1.1. + I я знаю, что один раз уход со страницы или потеря или неиспользование правильного идентификатора ViewState на стороне клиента приводит к потере всех @ViewScoped, К сожалению, это не проблема, поскольку фазы 1-4 и вызов метода работают и находят эти bean-компоненты . - person Matyas; 28.05.2013
comment
Более того: все работает, как и ожидалось, с использованием обходного пути, описанного выше (если я перемещаю h/p:commandButton за пределы p:dataTable) - person Matyas; 28.05.2013
comment
Хм, интересно, что отправляет вызов AJAX. Возможно, в запросе чего-то не хватает. - person LightGuard; 29.05.2013
comment
То же поведение с/без AJAX + проверил данные POST и содержит правильный ViewState (поэтому все в первых 4 фазах работает) и не вызывает выполнения производителя - person Matyas; 29.05.2013
comment
Какая фаза меняет viewid? Я ожидаю, что это произойдет во время invoke_application. - person LightGuard; 29.05.2013
comment
Мне удалось собрать исходники для моего стека ee и отладить проблему. Смотрите мой ответ и спасибо за ваше время. - person Matyas; 30.05.2013