Différences entre action et actionListener

393

Quelle est la différence entre actionet actionListeneret quand dois-je utiliser actionversus actionListener?

Murat Güzel
la source

Réponses:

583

actionListener

A utiliser actionListenersi vous voulez avoir un hook avant l'exécution de la véritable action métier, par exemple pour la journaliser, et / ou pour définir une propriété supplémentaire (par <f:setPropertyActionListener>), et / ou pour avoir accès au composant qui a appelé l'action (qui est disponible parActionEvent argument). Donc, uniquement pour préparer des fins avant que la véritable action commerciale ne soit invoquée.

La actionListenerméthode a par défaut la signature suivante:

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

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

Et il est censé être déclaré comme suit, sans aucune parenthèse de méthode:

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

Notez que vous ne pouvez pas passer d' arguments supplémentaires par EL 2.2. Vous pouvez cependant remplacer ActionEventcomplètement l' argument en passant et en spécifiant des arguments personnalisés. Les exemples suivants sont valides:

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

Notez l'importance des parenthèses dans l'expression de la méthode sans argument. S'ils étaient absents, JSF s’attendrait toujours à une méthodeActionEvent argument.

Si vous utilisez EL 2.2+, vous pouvez déclarer plusieurs méthodes d'écoute d'actions via <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() {}

Notez l'importance des parenthèses dans l' bindingattribut. S'ils étaient absents, EL lancerait une confusion javax.el.PropertyNotFoundException: Property 'actionListener1' not found on type com.example.Bean, car l' bindingattribut est interprété par défaut comme une expression de valeur, pas comme une expression de méthode. L'ajout de parenthèses de style EL 2.2+ transforme de manière transparente une expression de valeur en une expression de méthode. Voir aussi ao Pourquoi suis-je capable de lier <f: actionListener> à une méthode arbitraire si elle n'est pas prise en charge par JSF?


action

À utiliser actionsi vous souhaitez exécuter une opération commerciale et, si nécessaire, gérer la navigation. La actionméthode peut (donc, ne doit pas) retourner un Stringqui sera utilisé comme résultat du cas de navigation (la vue cible). Une valeur de retour denull ou la voidlaissera revenir à la même page et conserver la portée de la vue actuelle. Une valeur de retour d'une chaîne vide ou du même ID de vue retournera également à la même page, mais recréera l'étendue de la vue et détruira ainsi tous les beans de portée de vue actuellement actifs et, le cas échéant, les recréera.

La actionméthode peut être toute valide MethodExpression, également celles qui utilisent des arguments EL 2.2 comme ci-dessous:

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

Avec cette méthode:

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

Notez que lorsque votre méthode d'action renvoie uniquement une chaîne, vous pouvez également spécifier exactement cette chaîne dans l' actionattribut. C'est donc totalement maladroit:

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

Avec cette méthode insensée renvoyant une chaîne codée en dur:

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

Au lieu de cela, placez simplement cette chaîne codée en dur directement dans l'attribut:

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

Veuillez noter que cela indique à son tour une mauvaise conception: naviguer par POST. Ce n'est ni convivial ni SEO. Tout cela est expliqué dans Quand dois-je utiliser h: outputLink au lieu de h: commandLink? et est censé être résolu comme

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

Voir aussi Comment naviguer dans JSF? Comment faire en sorte que l'URL reflète la page actuelle (et non la précédente) .


f: écouteur ajax

Depuis JSF 2.x, il existe une troisième voie, la <f:ajax listener>.

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

La ajaxListenerméthode a par défaut la signature suivante:

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

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

Dans Mojarra, l' AjaxBehaviorEventargument est facultatif, ci-dessous fonctionne aussi bien.

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

Mais dans MyFaces, cela lancerait un MethodNotFoundException. Ci-dessous fonctionne dans les deux implémentations JSF lorsque vous souhaitez omettre l'argument.

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

Les écouteurs Ajax ne sont pas vraiment utiles sur les composants de commande. Ils sont plus utiles en entrée et en sélectionnant les composants <h:inputXxx>/ <h:selectXxx>. Dans les composants de commande, respectez simplement actionet / ou actionListenerpour plus de clarté et un meilleur code d'auto-documentation. De plus, comme actionListener, le f:ajax listenerne prend pas en charge le retour d'un résultat de navigation.

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

Pour des explications sur executeet les renderattributs, rendez-vous dans Comprendre le processus / mise à jour PrimeFaces et JSF f: ajax exécuter / rendre les attributs .


Ordonnance d'invocation

Les actionListeners sont toujours appelés avant le actiondans le même ordre qu'ils ont été déclarés dans la vue et attachés au composant. Le f:ajax listenerest toujours invoqué avant tout écouteur d'action. Ainsi, l'exemple suivant:

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

Appellera les méthodes dans l'ordre suivant:

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

Gestion des exceptions

Le actionListenerprend en charge une exception spéciale:AbortProcessingException . Si cette exception est levée à partir d'une actionListenerméthode, alors JSF ignorera tous les écouteurs d'action restants et la méthode d'action et procédera au rendu de la réponse directement. Vous ne verrez pas de page d'erreur / d'exception, JSF l'enregistrera cependant. Cela se fera également implicitement chaque fois qu'une autre exception est levée à partir d'un actionListener. Donc, si vous avez l'intention de bloquer la page par une page d'erreur à la suite d'une exception commerciale, vous devez certainement effectuer le travail dans la actionméthode.

Si la seule raison d'utiliser un actionListenerest d'avoir une voidméthode retournant à la même page, alors c'est une mauvaise. Les actionméthodes peuvent également parfaitement retourner void, contrairement à ce que certains IDE vous laissent croire via la validation EL. Notez que le exemples de vitrines PrimeFaces sont jonchés de ce type deactionListener s partout. C'est vraiment faux. N'utilisez pas cela comme excuse pour le faire vous-même.

Dans les requêtes ajax, cependant, un gestionnaire d'exceptions spécial est nécessaire. Cela indépendamment du fait que vous listenerutilisiez <f:ajax>ou non l' attribut . Pour obtenir des explications et un exemple, consultez la section Gestion des exceptions dans les demandes ajax JSF .

BalusC
la source
1
Vous avez raison de dire que les exceptions dans actionListeners sont avalées par défaut, mais dans JSF 2.0 ce comportement peut être modifié. Voir ma réponse ci-dessous pour plus de détails.
Arjan Tijms
3
@arjan: vous avez raison de dire que JSF 2.0 vous permet de modifier la gestion par défaut des exceptions levées actionListener, mais cela ne constitue toujours pas une bonne excuse pour abuser actionListenerd' actions commerciales .
BalusC
1
En effet, les actions commerciales sont dans le "flux" principal du cycle de demande / réponse et seul le actioncorrespond à cela. actionListenerest pour des trucs secondaires. Je voulais juste préciser que les exceptions de actionListeners peuvent être propagées si nécessaire;)
Arjan Tijms
2
@Kawy: le nom de la méthode est libre de choix lorsqu'il est utilisé dans l' actionListenerattribut et il doit l'être publicégalement. Le processActionnom n'est obligatoire que lorsque vous utilisez <f:actionListener type>, simplement parce que le type doit implémenter l' ActionListenerinterface qui a exactement ce nom de méthode processActiondéfini.
BalusC
2
@Muhammed: l'écouteur d'action ajax est appelé avant tous les écouteurs d'actions ordinaires. Notez que même lors de l'utilisation <f:ajax>, vous préférez en cas de composants de commande utiliser l' actionattribut pour les actions commerciales. Par exemple <h:commandButton action="#{bean.businessAction}"><f:ajax/></h:commandButton>.
BalusC
47

Comme BalusC l'a indiqué, les actionListenerexceptions par défaut avalent , mais dans JSF 2.0, il y a un peu plus à cela. À savoir, il ne fait pas qu'avaler et enregistrer, mais publie en fait l'exception.

Cela se produit via un appel comme celui-ci:

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

L'écouteur par défaut pour cet événement est celui ExceptionHandlerqui, pour Mojarra, est défini surcom.sun.faces.context.ExceptionHandlerImpl . Cette implémentation renverra essentiellement toute exception, sauf lorsqu'elle concerne une AbortProcessingException, qui est journalisée. ActionListeners encapsule l'exception qui est levée par le code client dans une telle AbortProcessingException qui explique pourquoi ceux-ci sont toujours enregistrés.

Cela ExceptionHandlerpeut cependant être remplacé dans faces-config.xml avec une implémentation personnalisée:

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

Au lieu d'écouter globalement, un seul bean peut également écouter ces événements. Ce qui suit en est une preuve de concept:

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

}

(notez que ce n'est pas ainsi que l'on devrait normalement coder les écouteurs, c'est uniquement à des fins de démonstration!)

Appelant cela à partir d'un Facelet comme ceci:

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

Cela entraînera l'affichage d'une page d'erreur.

Arjan Tijms
la source
43

ActionListener est déclenché en premier, avec une option pour modifier la réponse, avant que Action ne soit appelée et détermine l'emplacement de la page suivante.

Si vous avez plusieurs boutons sur la même page qui doivent aller au même endroit mais faire des choses légèrement différentes, vous pouvez utiliser la même action pour chaque bouton, mais utilisez un ActionListener différent pour gérer des fonctionnalités légèrement différentes.

Voici un lien qui décrit la relation:

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

Erick Robertson
la source
3
plus un, les lettres en gras disent presque tout.
Shirgill Farhan
0

TL; DR :

Les ActionListeners (il peut y en avoir plusieurs) s'exécutent dans l'ordre où ils ont été enregistrés AVANT leaction

Réponse longue :

Une entreprise actionappelle généralement un service EJB et, si nécessaire, définit également le résultat final et / ou navigue vers une vue différente si ce n'est pas ce que vous faites et actionListenerest plus approprié, c'est-à-dire lorsque l'utilisateur interagit avec les composants, tels que h:commandButtonou h:linkils peuvent être géré en passant le nom de la méthode du bean géré dans l' actionListenerattribut d'un composant d'interface utilisateur ou pour implémenter une ActionListenerinterface et passer le nom de la classe d'implémentation à l' actionListenerattribut d'un composant d'interface utilisateur.

Yehuda Schwartz
la source