Различия между действием и actionListener

393

В чем разница между actionи actionListener, и когда я должен использовать actionпротив actionListener?

Мурат Гюзель
источник

Ответы:

583

ActionListener

Используйте, actionListenerесли вы хотите иметь хук до того, как будет выполнено реальное бизнес-действие, например, чтобы зарегистрировать его и / или установить дополнительное свойство (by <f:setPropertyActionListener>), и / или иметь доступ к компоненту, который вызвал действие (которое доступно для ActionEventаргумент). Таким образом, исключительно для подготовки целей, прежде чем будет начато реальное деловое действие.

По actionListenerумолчанию метод имеет следующую подпись:

import javax.faces.event.ActionEvent;
// ...

public void actionListener(ActionEvent event) {
    // ...
}

И это должно быть объявлено следующим образом, без каких-либо скобок метода:

<h:commandXxx ... actionListener="#{bean.actionListener}" />

Обратите внимание, что вы не можете передать дополнительные аргументы по EL 2.2. Однако вы можете полностью переопределить ActionEventаргумент, передав и указав пользовательский аргумент (ы). Следующие примеры действительны:

<h:commandXxx ... actionListener="#{bean.methodWithoutArguments()}" />
<h:commandXxx ... actionListener="#{bean.methodWithOneArgument(arg1)}" />
<h:commandXxx ... actionListener="#{bean.methodWithTwoArguments(arg1, arg2)}" />
public void methodWithoutArguments() {}
public void methodWithOneArgument(Object arg1) {}
public void methodWithTwoArguments(Object arg1, Object arg2) {}

Обратите внимание на важность скобок в выражении метода без аргументов. Если бы они отсутствовали, JSF все равно ожидал бы метод сActionEvent аргументом.

Если вы используете EL 2.2+, вы можете объявить несколько методов прослушивания действий через <f:actionListener binding>.

<h:commandXxx ... actionListener="#{bean.actionListener1}">
    <f:actionListener binding="#{bean.actionListener2()}" />
    <f:actionListener binding="#{bean.actionListener3()}" />
</h:commandXxx>
public void actionListener1(ActionEvent event) {}
public void actionListener2() {}
public void actionListener3() {}

Обратите внимание на важность скобок в bindingатрибуте. Если бы они отсутствовали, EL смущенно бросил бы javax.el.PropertyNotFoundException: Property 'actionListener1' not found on type com.example.Bean, потому что bindingатрибут по умолчанию интерпретируется как выражение значения, а не как выражение метода. Добавление круглых скобок в стиле EL 2.2+ прозрачно превращает выражение значения в выражение метода. См. Также ao. Почему я могу связать <f: actionListener> с произвольным методом, если он не поддерживается JSF?


действие

Используйте, actionесли вы хотите выполнить бизнес-действие и при необходимости обработать навигацию. actionМетод может (таким образом, не должен) возвращать , Stringкоторая будет использоваться в качестве навигации случае исхода (целевой вид). Возвращаемое значение nullилиvoid позволит ему вернуться на ту же страницу и сохранить текущую область просмотра. Возвращаемое значение пустой строки или того же идентификатора представления также вернется на ту же страницу, но заново создаст область представления и, таким образом, уничтожит все активные в настоящее время компоненты области видимости и, если применимо, создаст их заново.

actionМетод может быть любыми допустимыми MethodExpression, также те , которые используют EL 2.2 аргументов , например , как показано ниже:

<h:commandXxx value="submit" action="#{bean.edit(item)}" />

С помощью этого метода:

public void edit(Item item) {
    // ...
}

Обратите внимание, что когда ваш метод действия возвращает только строку, вы также можете просто указать именно эту строку в actionатрибуте. Таким образом, это совершенно неуклюже

<h:commandLink value="Go to next page" action="#{bean.goToNextpage}" />

С помощью этого бессмысленного метода, возвращающего жестко закодированную строку:

public String goToNextpage() {
    return "nextpage";
}

Вместо этого просто поместите эту жестко закодированную строку прямо в атрибут:

<h:commandLink value="Go to next page" action="nextpage" />

Обратите внимание, что это в свою очередь указывает на плохой дизайн: навигация по POST. Это не для пользователя и не оптимизировано для SEO. Все это объясняется в разделе Когда я должен использовать h: outputLink вместо h: commandLink? и должен быть решен как

<h:link value="Go to next page" outcome="nextpage" />

Смотрите также Как ориентироваться в JSF? Как сделать так, чтобы URL отражал текущую страницу (а не предыдущую) .


f: слушатель ajax

Начиная с JSF 2.x существует третий путь <f:ajax listener>.

<h:commandXxx ...>
    <f:ajax listener="#{bean.ajaxListener}" />
</h:commandXxx>

По ajaxListenerумолчанию метод имеет следующую подпись:

import javax.faces.event.AjaxBehaviorEvent;
// ...

public void ajaxListener(AjaxBehaviorEvent event) {
    // ...
}

В Мохарре AjaxBehaviorEventаргумент необязателен, ниже работает как хорошо.

public void ajaxListener() {
    // ...
}

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

<h:commandXxx ...>
    <f:ajax execute="@form" listener="#{bean.ajaxListener()}" render="@form" />
</h:commandXxx>

Слушатели Ajax не очень полезны для командных компонентов. Они более полезны при вводе и выборе компонентов <h:inputXxx>/ <h:selectXxx>. В компонентах команд просто придерживайтесь actionи / или actionListenerдля ясности и лучшего самодокументированного кода. Более того, вроде бы actionListener, f:ajax listenerне поддерживает возврат результата навигации.

<h:commandXxx ... action="#{bean.action}">
    <f:ajax execute="@form" render="@form" />
</h:commandXxx>

Для объяснения executeи renderатрибутов, обратитесь к разделу Понимание процессов / обновлений PrimeFaces и атрибутов JSF f: ajax execute / render .


Порядок вызова

В actionListenerы всегда вызывается передaction тем в том же порядке , как они были объявлены в представлении и прикреплены к компоненту. f:ajax listenerВсегда вызывается перед тем какой - либо действий слушателя. Итак, следующий пример:

<h:commandButton value="submit" actionListener="#{bean.actionListener}" action="#{bean.action}">
    <f:actionListener type="com.example.ActionListenerType" />
    <f:actionListener binding="#{bean.actionListenerBinding()}" />
    <f:setPropertyActionListener target="#{bean.property}" value="some" />
    <f:ajax listener="#{bean.ajaxListener}" />
</h:commandButton>

Вызовет методы в следующем порядке:

  1. Bean#ajaxListener()
  2. Bean#actionListener()
  3. ActionListenerType#processAction()
  4. Bean#actionListenerBinding()
  5. Bean#setProperty()
  6. Bean#action()

Обработка исключений

actionListenerПоддерживает специальное исключение: AbortProcessingException. Если это исключение выдается из actionListenerметода, то JSF пропустит все оставшиеся прослушиватели действий и метод действия и продолжит обработку ответа напрямую. Вы не увидите страницу ошибки / исключения, однако JSF зарегистрирует ее. Это также неявно будет выполняться всякий раз, когда из исключения выдается любое другое исключение actionListener. Таким образом, если вы намереваетесь заблокировать страницу страницей с ошибкой в ​​результате бизнес-исключения, то вы обязательно должны выполнить работу в actionметоде.

Если единственная причина , чтобы использовать actionListenerэто , чтобы иметь voidметод , возвращающий к одной и той же странице, то это плохой. Эти actionметоды вполне могут также возвращать void, наоборот тому , что некоторые Иды пусть вы считаете , с помощью проверки EL. Обратите внимание, что примеры витрин PrimeFaces завалены этим видом actionListeners повсюду. Это действительно неправильно. Не используйте это как оправдание, чтобы сделать это самостоятельно.

Однако в запросах ajax требуется специальный обработчик исключений. Это независимо от того, используете ли вы listenerатрибут <f:ajax>или нет. Для объяснения и примера обратитесь к обработке исключений в запросах JSF ajax .

BalusC
источник
1
Вы правы, что исключения в actionListeners проглатываются по умолчанию, но в JSF 2.0 это поведение может быть изменено. Смотрите мой ответ ниже для деталей.
Арджан Тиймс
3
@arjan: вы правы в том, что JSF 2.0 позволяет вам изменять обработку исключений по умолчанию actionListener, но это еще не делает его хорошим оправданием злоупотреблений actionListenerдля деловых действий.
BalusC
1
Действительно, бизнес-действия находятся в основном «потоке» цикла запрос / ответ и только actionсоответствуют этому. actionListenerдля второстепенных вещей. Просто хотел уточнить, что исключения из actionListeners могут распространяться при необходимости;)
Арджан Тиймс
2
@ Kawy: имя метода свободно выбирается при использовании в actionListenerатрибуте, и оно должно быть publicтакже. processActionИмя только обязательным , если вы используете <f:actionListener type>, просто потому , что тип должен реализовывать ActionListenerинтерфейс , который имеет именно это имя метода processActionопределяемом.
BalusC
2
@Muhammed: слушатель действия ajax вызывается перед всеми обычными слушателями действия. Обратите внимание, что даже при использовании <f:ajax>вы в случае командных компонентов предпочитаете использовать actionатрибут для бизнес-действий. Например <h:commandButton action="#{bean.businessAction}"><f:ajax/></h:commandButton>.
BalusC
47

Как указал BalusC, actionListenerпо умолчанию исключаются исключения, но в JSF 2.0 есть еще кое-что . А именно, он не просто глотает и регистрирует, но фактически публикует исключение.

Это происходит через вызов, как это:

context.getApplication().publishEvent(context, ExceptionQueuedEvent.class,                                                          
    new ExceptionQueuedEventContext(context, exception, source, phaseId)
);

По умолчанию для этого события используется прослушиватель ExceptionHandlerwhich для Mojarra com.sun.faces.context.ExceptionHandlerImpl. Эта реализация будет в основном перебрасывать любое исключение, кроме случаев, когда оно касается исключения AbortProcessingException, которое регистрируется. ActionListeners обертывают исключение, которое выдается клиентским кодом, в такое исключение AbortProcessingException, которое объясняет, почему они всегда регистрируются.

Это ExceptionHandlerможет быть заменено, однако, вface-config.xml с пользовательской реализацией:

<exception-handlerfactory>
   com.foo.myExceptionHandler
</exception-handlerfactory>

Вместо глобального прослушивания, один компонент также может прослушивать эти события. Следующее является доказательством концепции этого:

@ManagedBean
@RequestScoped
public class MyBean {

    public void actionMethod(ActionEvent event) {

        FacesContext.getCurrentInstance().getApplication().subscribeToEvent(ExceptionQueuedEvent.class, new SystemEventListener() {

        @Override
        public void processEvent(SystemEvent event) throws AbortProcessingException {
            ExceptionQueuedEventContext content = (ExceptionQueuedEventContext)event.getSource();
            throw new RuntimeException(content.getException());
        }

        @Override
        public boolean isListenerForSource(Object source) {
            return true;
        }
        });

        throw new RuntimeException("test");
    }

}

(обратите внимание, это не то, как обычно следует кодировать слушателей, это только для демонстрационных целей!)

Вызов это из Facelet, как это:

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core">
    <h:body>
        <h:form>
            <h:commandButton value="test" actionListener="#{myBean.actionMethod}"/>
        </h:form>
    </h:body>
</html>

Это приведет к отображению страницы с ошибкой.

Арджан Тиймс
источник
43

Сначала запускается ActionListener с возможностью изменения ответа до вызова Action и определения местоположения следующей страницы.

Если у вас есть несколько кнопок на одной странице, которые должны идти в одно и то же место, но выполнять немного разные вещи, вы можете использовать одно и то же действие для каждой кнопки, но использовать разные ActionListener для обработки немного другой функциональности.

Вот ссылка, которая описывает отношения:

http://www.java-samples.com/showtutorial.php?tutorialid=605

Эрик Робертсон
источник
3
плюс один, жирными буквами написано почти все.
Ширгилл Фархан
0

TL; DR :

В ActionListeners (может быть несколько) выполняются в порядке их регистрации доaction

Длинный ответ :

Бизнес actionобычно вызывает службу EJB и при необходимости также устанавливает конечный результат и / или переходит к другому представлению, если это не то, что вы делаете, actionListenerболее уместно, например, когда пользователь взаимодействует с компонентами, такими как h:commandButtonили h:linkони могут обрабатываться путем передачи имени метода управляемого компонента в actionListenerатрибуте компонента UI или для реализации ActionListenerинтерфейса и передачи имени класса реализации в actionListenerатрибут компонента UI.

Иегуда Шварц
источник