Diferencias entre action y actionListener

Respuestas:

583

actionListener

Úselo actionListenersi desea tener un enlace antes de que se ejecute la acción comercial real, por ejemplo, para iniciar sesión y / o para establecer una propiedad adicional (por <f:setPropertyActionListener>), y / o tener acceso al componente que invocó la acción (que está disponible por ActionEventargumento). Por lo tanto, solo con fines de preparación antes de que se invoque la acción comercial real.

El actionListenermétodo tiene por defecto la siguiente firma:

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

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

Y se supone que debe declararse de la siguiente manera, sin ningún paréntesis de método:

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

Tenga en cuenta que no puede pasar argumentos adicionales por EL 2.2. Sin embargo, puede anular el ActionEventargumento por completo pasando y especificando argumentos personalizados. Los siguientes ejemplos son válidos:

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

Tenga en cuenta la importancia de los paréntesis en la expresión del método sin argumentos. Si estuvieran ausentes, JSF aún esperaría un método con ActionEventargumento.

Si está en EL 2.2+, puede declarar varios métodos de escucha de acción a través de <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() {}

Tenga en cuenta la importancia de los paréntesis en el bindingatributo. Si estuvieran ausentes, EL arrojaría confusamente a javax.el.PropertyNotFoundException: Property 'actionListener1' not found on type com.example.Bean, porque el bindingatributo se interpreta por defecto como una expresión de valor, no como una expresión de método. Agregar paréntesis de estilo EL 2.2+ convierte transparentemente una expresión de valor en una expresión de método. Consulte también ao ¿Por qué puedo vincular <f: actionListener> a un método arbitrario si JSF no lo admite?


acción

Úselo actionsi desea ejecutar una acción comercial y, si es necesario, manejar la navegación. El actionmétodo puede (por lo tanto, no debe) devolver un Stringque se utilizará como resultado del caso de navegación (la vista de destino). Un valor de retorno de nullo voidle permitirá volver a la misma página y mantener vivo el alcance de la vista actual. Un valor de retorno de una cadena vacía o la misma ID de vista también volverá a la misma página, pero recreará el alcance de la vista y, por lo tanto, destruirá cualquier bean de ámbito de vista actualmente activo y, si corresponde, vuelva a crearlos.

El actionmétodo puede ser válido MethodExpression, también los que usan argumentos EL 2.2 como los siguientes:

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

Con este método:

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

Tenga en cuenta que cuando su método de acción solo devuelve una cadena, también puede especificar exactamente esa cadena en el actionatributo. Por lo tanto, esto es totalmente torpe:

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

Con este método sin sentido que devuelve una cadena codificada:

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

En cambio, solo ponga esa cadena codificada directamente en el atributo:

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

Tenga en cuenta que esto a su vez indica un mal diseño: navegar por POST. Esto no es fácil de usar ni SEO. Todo esto se explica en ¿ Cuándo debo usar h: outputLink en lugar de h: commandLink? y se supone que debe resolverse como

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

Consulte también ¿Cómo navegar en JSF? Cómo hacer que la URL refleje la página actual (y no la anterior) .


f: oyente ajax

Desde JSF 2.x hay una tercera vía, la <f:ajax listener>.

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

El ajaxListenermétodo tiene por defecto la siguiente firma:

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

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

En Mojarra, el AjaxBehaviorEventargumento es opcional, a continuación funciona igual de bien.

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

Pero en MyFaces, arrojaría un MethodNotFoundException. A continuación funciona en ambas implementaciones JSF cuando desea omitir el argumento.

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

Los oyentes Ajax no son realmente útiles en los componentes del comando. Son más útiles en la entrada y seleccionan componentes <h:inputXxx>/ <h:selectXxx>. En los componentes de comando, simplemente manténgase actiony / o actionListenerpara mayor claridad y mejor código de autodocumentación. Además, al igual que actionListener, f:ajax listenerno admite la devolución de un resultado de navegación.

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

Para obtener explicaciones executey renderatributos, diríjase a Comprensión del proceso / actualización de PrimeFaces y JSF f: ajax ejecutar / renderizar atributos .


Orden de invocación

Los actionListeners siempre se invocan antes de la actionen el mismo orden en que se han declarado en la vista y se unen al componente. El f:ajax listenersiempre se invoca antes de cualquier escucha de acción. Entonces, el siguiente ejemplo:

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

Invocará los métodos en el siguiente orden:

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

Manejo de excepciones

El actionListeneradmite una excepción especial: AbortProcessingException. Si se lanza esta excepción desde un actionListenermétodo, JSF omitirá cualquier escucha de acción restante y el método de acción y procederá a procesar la respuesta directamente. No verá una página de error / excepción, sin embargo, JSF lo registrará. Esto también se hará implícitamente cuando se produzca cualquier otra excepción desde unactionListener . Entonces, si tiene la intención de bloquear la página por una página de error como resultado de una excepción comercial, entonces definitivamente debe realizar el trabajo en el actionmétodo.

Si la única razón para usar un actionListeneres que un voidmétodo regrese a la misma página, entonces ese es uno malo. Los actionmétodos también pueden regresar perfectamente void, al contrario de lo que algunos IDEs le permiten creer a través de la validación EL. Tenga en cuenta que los ejemplos de escaparate de PrimeFaces están llenos de este tipo de actionListeners en todo lugar. Esto de hecho está mal. No uses esto como una excusa para hacerlo tú mismo.

En las solicitudes ajax, sin embargo, se necesita un controlador de excepción especial. Esto es independientemente de si usa el listeneratributo de <f:ajax>o no. Para obtener una explicación y un ejemplo, diríjase a Manejo de excepciones en solicitudes JSF ajax .

BalusC
fuente
1
Tienes razón en que las excepciones en actionListeners se tragan por defecto, pero en JSF 2.0 este comportamiento puede ser alterado. Vea mi respuesta a continuación para más detalles.
Arjan Tijms
3
@arjan: tiene razón en que JSF 2.0 le permite cambiar el manejo predeterminado de las excepciones lanzadas actionListener, pero eso aún no es una buena excusa para abusar actionListenerde las acciones comerciales .
BalusC
1
De hecho, las acciones comerciales se encuentran en el "flujo" principal del ciclo de solicitud / respuesta y solo se actioncorresponde con eso. actionListeneres para cosas secundarias. Solo quería aclarar que las excepciones de actionListeners pueden propagarse si es necesario;)
Arjan Tijms
2
@Kawy: el nombre del método es de libre elección cuando se usa en un actionListeneratributo y también debe serlo public. El processActionnombre solo es obligatorio cuando lo usa <f:actionListener type>, simplemente porque el tipo tiene que implementar una ActionListenerinterfaz que tenga exactamente ese nombre de método processActiondefinido.
BalusC
2
@ Muhammed: el oyente de acción ajax se invoca antes que todos los oyentes de acción normales. Tenga en cuenta que incluso cuando lo use <f:ajax>, en el caso de componentes de comando preferiría usar el actionatributo para acciones comerciales. Por ej <h:commandButton action="#{bean.businessAction}"><f:ajax/></h:commandButton>.
BalusC
47

Como BalusC indicó, actionListenerpor defecto se traga excepciones, pero en JSF 2.0 hay un poco más de esto. Es decir, no solo se traga y registra, sino que en realidad publica la excepción.

Esto sucede a través de una llamada como esta:

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

El oyente predeterminado para este evento es el ExceptionHandlerque para Mojarra está configurado encom.sun.faces.context.ExceptionHandlerImpl . Esta implementación básicamente volverá a generar cualquier excepción, excepto cuando se trata de una excepción AbortProcessingException, que se registra. Los ActionListeners envuelven la excepción que arroja el código del cliente en una AbortProcessingException que explica por qué siempre se registran.

Sin ExceptionHandlerembargo, esto se puede reemplazar en faces-config.xml con una implementación personalizada:

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

En lugar de escuchar globalmente, un solo bean también puede escuchar estos eventos. Lo siguiente es una prueba de concepto de esto:

@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");
    }

}

(tenga en cuenta que no es así como se deben codificar normalmente los oyentes, ¡esto es solo para fines de demostración!)

Llamando a esto desde un Facelet como este:

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

Resultará en una página de error que se muestra.

Arjan Tijms
fuente
43

ActionListener se dispara primero, con una opción para modificar la respuesta, antes de que se llame a Action y determine la ubicación de la página siguiente.

Si tiene varios botones en la misma página que deberían ir al mismo lugar pero hacer cosas ligeramente diferentes, puede usar la misma Acción para cada botón, pero use un ActionListener diferente para manejar funcionalidades ligeramente diferentes.

Aquí hay un enlace que describe la relación:

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

Erick Robertson
fuente
3
más uno, las letras en negrita lo dicen casi todo.
Shirgill Farhan
0

TL; DR :

Los ActionListeners (pueden ser múltiples) se ejecutan en el orden en que se registraron ANTES deaction

Respuesta larga :

Una empresa actiongeneralmente invoca un servicio EJB y, si es necesario, también establece el resultado final y / o navega a una vista diferente si eso no es lo que está haciendo y actionListeneres más apropiado, es decir, cuando el usuario interactúa con los componentes, como h:commandButtono h:linkpueden se maneja pasando el nombre del método de bean administrado en el actionListeneratributo de un Componente UI o para implementar una ActionListenerinterfaz y pasar el nombre de la clase de implementación al actionListeneratributo de un Componente UI.

Yehuda Schwartz
fuente