В чем разница между action
и actionListener
, и когда следует использовать action
против actionListener
?
Различия между действием и действиемListener
Ответ 1
ActionListener
Используйте actionListener
, если вы хотите иметь крючок до, выполнение реального бизнес-действия выполняется, например. для его регистрации и/или для установки дополнительного свойства (<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+ прозрачно превращает выражение значения в выражение метода. См. Также a.o. Почему я могу связать < f: actionListener > произвольному методу, если он не поддерживается JSF?
Действие
Используйте action
, если вы хотите выполнить бизнес-действие и, при необходимости, управлять навигацией. Метод action
может (таким образом, не должен) возвращать String
, который будет использоваться в качестве результата действия навигации (целевой вид). Возвращаемое значение null
или void
позволит ему вернуться на ту же страницу и сохранить текущую область обзора. Возвращаемое значение пустой строки или того же идентификатора вида также вернется на одну и ту же страницу, но заново создаст область видимости и, таким образом, уничтожит все активные в настоящее время области просмотра beans и, если применимо, воссоздает их.
Метод 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) {
// ...
}
В Mojarra аргумент 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 атрибуты выполнения/отображения.
Порядок вызова
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>
Вызовите методы в следующем порядке:
-
Bean#ajaxListener()
-
Bean#actionListener()
-
ActionListenerType#processAction()
-
Bean#actionListenerBinding()
-
Bean#setProperty()
-
Bean#action()
Обработка исключений
actionListener
поддерживает специальное исключение: AbortProcessingException
, Если это исключение выбрано из метода actionListener
, то JSF пропустит все оставшиеся прослушиватели действий и метод действия и приступит к прямой обработке ответа. Вы не увидите страницу ошибок/исключений, однако JSF зарегистрирует ее. Это также неявно будет выполняться всякий раз, когда любое другое исключение выбрасывается из actionListener
. Таким образом, если вы намерены заблокировать страницу с помощью страницы с ошибкой в результате бизнес-исключения, вам обязательно нужно выполнить задание в методе action
.
Если единственной причиной использования actionListener
является метод void
, возвращающийся на ту же страницу, то это плохой. Методы action
могут отлично возвращать void
, напротив того, что некоторые IDE позволяют вам верить через проверку EL. Обратите внимание, что примеры PrimeFaces showcase заполнены таким типом actionListener
по всему месту. Это действительно неправильно. Не используйте это как оправдание, чтобы сделать это сами.
Однако в запросах ajax необходим специальный обработчик исключений. Это независимо от того, используете ли вы атрибут listener
<f:ajax>
или нет. Для объяснения и примера, перейдите в Обработка исключений в JSF-запросах ajax.
Ответ 2
Как показал BalusC, actionListener
по умолчанию использует исключение для swallows, но в JSF 2.0 есть немного больше. А именно, это не просто ласточки и журналы, но на самом деле публикует исключение.
Это происходит с помощью вызова типа:
context.getApplication().publishEvent(context, ExceptionQueuedEvent.class,
new ExceptionQueuedEventContext(context, exception, source, phaseId)
);
Слушателем по умолчанию для этого события является ExceptionHandler
, который для Mojarra установлен на com.sun.faces.context.ExceptionHandlerImpl
. Эта реализация в основном отменяет любое исключение, за исключением случаев, когда это относится к исключению AbortProcessingException, которое регистрируется. ActionListeners обертывают исключение, которое генерируется клиентским кодом в таком исключении AbortProcessingException, которое объясняет, почему они всегда регистрируются.
Этот ExceptionHandler
может быть заменен, однако, в faces-config.xml с пользовательской реализацией:
<exception-handlerfactory>
com.foo.myExceptionHandler
</exception-handlerfactory>
Вместо того, чтобы слушать глобально, один bean может также прослушивать эти события. Ниже приведено доказательство этого:
@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>
Будет отображаться страница с ошибкой.
Ответ 3
ActionListener запускается сначала с возможностью изменения ответа, прежде чем действие вызывается и определяет местоположение следующей страницы.
Если у вас несколько кнопок на одной странице, которые должны идти в одно и то же место, но делать несколько разные вещи, вы можете использовать одно действие для каждой кнопки, но использовать другой ActionListener для обработки немного разных функций.
Вот ссылка, которая описывает отношение: