action和actionListener之间的区别是什么,什么时候应该使用action和actionListener?


在Action被调用并确定下一页的位置之前,ActionListener首先被触发,并带有一个修改响应的选项。

如果你在同一个页面上有多个按钮,它们应该指向相同的位置,但做的事情略有不同,你可以为每个按钮使用相同的Action,但使用不同的ActionListener来处理略有不同的功能。

下面是一个描述这种关系的链接:

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


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绑定>声明多个动作监听器方法。

<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() {}

Note the importance of the parentheses in the binding attribute. If they were absent, EL would confusingly throw a javax.el.PropertyNotFoundException: Property 'actionListener1' not found on type com.example.Bean, because the binding attribute is by default interpreted as a value expression, not as a method expression. Adding EL 2.2+ style parentheses transparently turns a value expression into a method expression. See also a.o. Why am I able to bind <f:actionListener> to an arbitrary method if it's not supported by JSF?


行动

Use action if you want to execute a business action and if necessary handle navigation. The action method can (thus, not must) return a String which will be used as navigation case outcome (the target view). A return value of null or void will let it return to the same page and keep the current view scope alive. A return value of an empty string or the same view ID will also return to the same page, but recreate the view scope and thus destroy any currently active view scoped beans and, if applicable, recreate them.

action方法可以是任何有效的MethodExpression,也可以是使用EL 2.2参数的MethodExpression,如下所示:

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

用这个方法:

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

注意,当您的action方法只返回一个字符串时,您也可以在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开始。还有第三种方法,<f:ajax监听器>。

<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监听器不支持返回导航结果。

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

有关执行和呈现属性的解释,请参阅理解PrimeFaces处理/更新和JSF f:ajax执行/呈现属性。


调用顺序

actionlistener总是按照在视图中声明并附加到组件的相同顺序在操作之前调用。ajax监听器总是在任何动作监听器之前被调用。下面这个例子:

<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>

将按以下顺序调用这些方法:

豆# ajaxListener () 豆# actionListener () ActionListenerType # processAction () 豆# actionListenerBinding () 豆# setProperty () 豆#行动()


异常处理

The actionListener supports a special exception: AbortProcessingException. If this exception is thrown from an actionListener method, then JSF will skip any remaining action listeners and the action method and proceed to render response directly. You won't see an error/exception page, JSF will however log it. This will also implicitly be done whenever any other exception is being thrown from an actionListener. So, if you intend to block the page by an error page as result of a business exception, then you should definitely be performing the job in the action method.

如果使用actionListener的唯一原因是有一个void方法返回到同一页,那么这是一个糟糕的原因。动作方法也可以完全返回void,这与一些ide通过EL验证让您相信的情况相反。注意,PrimeFaces展示示例中到处都是这种actionlistener。这的确是错误的。不要以此为借口自己也这么做。

然而,在ajax请求中,需要一个特殊的异常处理程序。这与是否使用<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,该异常会被记录下来。actionlistener将客户端代码抛出的异常包装在这样一个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>

将导致显示错误页面。


TL; diana:

actionlistener(可以有多个)按照它们在动作之前注册的顺序执行

长一点的回答:

A business action typically invokes an EJB service and if necessary also sets the final result and/or navigates to a different view if that is not what you are doing an actionListener is more appropriate i.e. for when the user interacts with the components, such as h:commandButton or h:link they can be handled by passing the name of the managed bean method in actionListener attribute of a UI Component or to implement an ActionListener interface and pass the implementation class name to actionListener attribute of a UI Component.