action
和 actionListener
有什么区别?我应该在什么情况下使用 action
,而不是 actionListener
?action
和 actionListener
有什么区别?我应该在什么情况下使用 action
,而不是 actionListener
?如果您想在真正的业务操作执行之前有一个钩子,例如记录它和/或设置附加属性(通过<f:setPropertyActionListener>
),并/或访问调用操作的组件(可通过ActionEvent
参数获得),请使用actionListener
。因此,仅用于在真正的业务操作被调用之前进行准备。
actionListener
方法默认具有以下签名:
import javax.faces.event.ActionEvent;
// ...
public void actionListener(ActionEvent event) {
// ...
}
应该这样声明,不需要任何方法括号:
<h:commandXxx ... actionListener="#{bean.actionListener}" />
<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) {}
<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+风格的括号可以将值表达式透明地转换为方法表达式。另请参见为什么我能绑定<f:actionListener>到任意方法而不受JSF支持的限制?。
如果您想执行业务操作并必要时处理导航,请使用action
。 action
方法可以(因此不必)返回一个String
,该字符串将用作导航案例结果(目标视图)。 返回值为null
或void
将使其返回到同一页并保持当前视图范围活动。 空字符串或相同的视图ID的返回值也将返回到同一页,但重新创建视图范围,并因此销毁任何当前活动的视图范围bean(如果适用),并在可能的情况下重新创建它们。
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" />
<h:link value="Go to next page" outcome="nextpage" />
另请参阅如何在JSF中导航?如何使URL反映当前页面(而不是上一个页面)?。
自从JSF 2.x以来,有第三种方法,即<f:ajax监听器>
。
<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
总是在任何actionListener
之前被执行。因此,以下示例:
<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将跳过任何剩余的action listeners和action方法,并直接进行呈现响应。您不会看到错误/异常页面,但JSF将记录它。每当从actionListener
抛出任何其他异常时,这也将隐式地完成。因此,如果您打算通过业务异常的结果来阻止页面的块,则应在action
方法中执行该作业。
如果使用actionListener
的唯一原因是具有返回到同一页的void
方法,则这是一个不好的选择。与某些IDE通过EL验证所让您相信的相反,action
方法也可以完全返回void
。请注意PrimeFaces展示示例中充斥着这种类型的actionListener
。这确实是错误的。不要以此为借口去效仿。
<f:ajax>
的listener
属性都是如此。有关说明和示例,请前往JSF ajax请求中的异常处理。正如BalusC所指出的那样,在默认情况下,actionListener
会吞噬异常,但在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
中,这就是为什么总是记录这些异常的原因。
然而,在faces-config.xml中可以使用自定义实现替换此ExceptionHandler
:
<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>
会导致显示错误页面。
ActionListener首先被触发,可以修改响应选项,然后Action被调用并确定下一页的位置。
如果您在同一页上有多个按钮,这些按钮应该前往相同的位置,但执行略有不同的操作,则可以使用相同的Action来处理每个按钮,但使用不同的ActionListener来处理略有不同的功能。
以下链接描述了它们之间的关系:
TL;DR:
ActionListener
(可以有多个)按照注册的顺序在action
之前执行。
长答案:
一个业务action
通常会调用一个EJB服务,如果需要的话还会设置最终结果和/或导航到不同的视图。如果你不是在做这些事情,那么使用actionListener
更合适,例如当用户与组件交互时,比如h:commandButton
或h:link
,可以通过将托管bean方法的名称传递给UI组件的actionListener
属性来处理它们,或者实现一个ActionListener
接口并将实现类名称传递给UI组件的actionListener
属性。
actionListener
抛出异常时的默认处理方式,但这仍然不是滥用actionListener
执行业务操作的好借口。 - BalusCaction
与之对应。actionListener
用于次要事项。只是想澄清,如果需要,可以传播来自actionListener
的异常 ;) - Arjan TijmsactionListener
属性中使用时,方法名称可自由选择,并且必须为public
。只有当您正在使用<f:actionListener type>
时,processAction
名称才是强制性的,因为该类型必须实现ActionListener
接口,该接口恰好定义了该方法名称processAction
。 - BalusC