Unterschiede zwischen action und actionListener

393

Was ist der Unterschied zwischen actionund actionListener, und wann soll ich im actionVergleich zu actionListener?

Murat Güzel
quelle

Antworten:

583

actionListener

Verwenden actionListenerSie diese Option, wenn Sie einen Hook haben möchten, bevor die eigentliche Geschäftsaktion ausgeführt wird, z. B. um sie zu protokollieren und / oder um eine zusätzliche Eigenschaft (von <f:setPropertyActionListener>) festzulegen und / oder um Zugriff auf die Komponente zu haben, die die Aktion aufgerufen hat (die von verfügbar ist) ActionEventStreit). Also nur zu Vorbereitungszwecken, bevor die eigentliche Geschäftsaktion aufgerufen wird.

Die actionListenerMethode hat standardmäßig die folgende Signatur:

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

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

Und es soll wie folgt deklariert werden, ohne Methodenklammern:

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

Beachten Sie, dass Sie von EL 2.2 keine zusätzlichen Argumente übergeben können. Sie können das ActionEventArgument jedoch vollständig überschreiben, indem Sie benutzerdefinierte Argumente übergeben und angeben. Die folgenden Beispiele sind gültig:

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

Beachten Sie die Bedeutung der Klammern im argumentlosen Methodenausdruck. Wenn sie nicht vorhanden wären, würde JSF immer noch eine Methode mit ActionEventArgumenten erwarten .

Wenn Sie mit EL 2.2+ arbeiten, können Sie über mehrere Aktionslistener-Methoden deklarieren <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() {}

Beachten Sie die Wichtigkeit der Klammern im bindingAttribut. Wenn sie nicht vorhanden wären, würde EL verwirrenderweise a auslösen javax.el.PropertyNotFoundException: Property 'actionListener1' not found on type com.example.Bean, da das bindingAttribut standardmäßig als Werteausdruck und nicht als Methodenausdruck interpretiert wird. Durch transparentes Hinzufügen von Klammern im EL 2.2+ -Stil wird ein Werteausdruck in einen Methodenausdruck umgewandelt. Siehe auch ao Warum kann ich <f: actionListener> an eine beliebige Methode binden, wenn diese von JSF nicht unterstützt wird?


Aktion

Verwenden actionSie diese Option, wenn Sie eine Geschäftsaktion ausführen und gegebenenfalls die Navigation übernehmen möchten. Die actionMethode kann (muss also nicht) ein zurückgeben String, das als Navigationsfallergebnis (die Zielansicht) verwendet wird. Ein Rückgabewert von nulloder voidlässt ihn zur gleichen Seite zurückkehren und den aktuellen Ansichtsbereich am Leben erhalten. Ein Rückgabewert einer leeren Zeichenfolge oder derselben Ansichts-ID kehrt ebenfalls zur gleichen Seite zurück, erstellt jedoch den Ansichtsbereich neu und zerstört somit alle derzeit aktiven Beans mit Ansichtsbereich und erstellt sie gegebenenfalls neu.

Die actionMethode kann eine beliebige sein MethodExpression, auch solche, die EL 2.2-Argumente wie die folgenden verwenden:

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

Mit dieser Methode:

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

Beachten Sie, dass Sie, wenn Ihre Aktionsmethode nur eine Zeichenfolge zurückgibt, auch genau diese Zeichenfolge im actionAttribut angeben können . Das ist also total ungeschickt:

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

Mit dieser sinnlosen Methode wird eine fest codierte Zeichenfolge zurückgegeben:

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

Fügen Sie stattdessen einfach diese fest codierte Zeichenfolge direkt in das Attribut ein:

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

Bitte beachten Sie, dass dies wiederum auf ein schlechtes Design hinweist: Navigieren per POST. Dies ist weder benutzer- noch SEO-freundlich. Dies alles wird unter Wann soll ich h: outputLink anstelle von h: commandLink verwenden? und soll gelöst werden als

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

Siehe auch Wie navigiere ich in JSF? So stellen Sie sicher, dass die URL die aktuelle Seite (und nicht die vorherige) widerspiegelt .


f: Ajax Listener

Seit JSF 2.x gibt es einen dritten Weg, den <f:ajax listener>.

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

Die ajaxListenerMethode hat standardmäßig die folgende Signatur:

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

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

In Mojarra ist das AjaxBehaviorEventArgument optional, unten funktioniert es genauso gut.

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

Aber in MyFaces würde es ein werfen MethodNotFoundException. Das Folgende funktioniert in beiden JSF-Implementierungen, wenn Sie das Argument weglassen möchten.

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

Ajax-Listener sind für Befehlskomponenten nicht wirklich nützlich. Sie sind nützlicher bei der Eingabe und Auswahl von Komponenten <h:inputXxx>/ <h:selectXxx>. Halten Sie sich in Befehlskomponenten einfach an actionund / oder actionListeneraus Gründen der Klarheit und besseren Selbstdokumentation des Codes. Darüber hinaus unterstützt actionListenerdas f:ajax listenernicht die Rückgabe eines Navigationsergebnisses.

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

Erläuterungen zu executeund renderAttributen finden Sie unter Grundlegendes zum PrimeFaces-Prozess / Update und zu JSF f: ajax execute / render-Attributen .


Aufrufauftrag

Die actionListeners werden immer vor dem actionin derselben Reihenfolge aufgerufen, in der sie in der Ansicht deklariert und an die Komponente angehängt wurden. Das f:ajax listenerwird immer vor einem Aktionslistener aufgerufen . Also das folgende Beispiel:

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

Ruft die Methoden in der folgenden Reihenfolge auf:

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

Ausnahmebehandlung

Das actionListenerunterstützt eine besondere Ausnahme : AbortProcessingException. Wenn diese Ausnahme von einer actionListenerMethode ausgelöst wird, überspringt JSF alle verbleibenden Aktionslistener und die Aktionsmethode und fährt mit dem direkten Rendern der Antwort fort. Sie sehen keine Fehler- / Ausnahmeseite, JSF protokolliert sie jedoch. Dies wird implizit auch dann durchgeführt, wenn eine andere Ausnahme von einem ausgelöst wird actionListener. Wenn Sie also beabsichtigen, die Seite aufgrund einer Geschäftsausnahme durch eine Fehlerseite zu blockieren, sollten Sie den Job auf jeden Fall in der actionMethode ausführen .

Wenn der einzige Grund für die Verwendung actionListenervon eine voidMethode ist, die zur gleichen Seite zurückkehrt, ist dies eine schlechte Methode. Die actionMethoden können auch perfekt zurückkehren void, im Gegensatz zu dem, was einige IDEs Ihnen über die EL-Validierung glauben machen. Beachten Sie, dass die PrimeFaces-SchaufensterbeispieleactionListener überall mit solchen s übersät sind . Das ist in der Tat falsch. Verwenden Sie dies nicht als Ausrede, um dies auch selbst zu tun.

Bei Ajax-Anforderungen wird jedoch ein spezieller Ausnahmebehandler benötigt. Dies ist unabhängig davon, ob Sie das listenerAttribut von verwenden <f:ajax>oder nicht. Eine Erläuterung und ein Beispiel finden Sie unter Ausnahmebehandlung in JSF-Ajax-Anforderungen .

BalusC
quelle
1
Sie haben Recht, dass Ausnahmen in actionListeners standardmäßig verschluckt werden, aber in JSF 2.0 kann dieses Verhalten geändert werden. Siehe meine Antwort unten für Details.
Arjan Tijms
3
@arjan: Sie haben Recht, dass Sie mit JSF 2.0 die Standardbehandlung von Ausnahmen ändern können, die von ausgelöst werden. Dies ist actionListenerjedoch keine gute Entschuldigung für den Missbrauch actionListenervon Geschäftsaktionen .
BalusC
1
In der Tat befinden sich Geschäftsaktionen im Haupt- "Fluss" des Anforderungs- / Antwortzyklus und nur das actionentspricht dem. actionListenerist für sekundäre Sachen. Ich wollte nur klarstellen, dass Ausnahmen von actionListeners verbreitet werden können, wenn dies erforderlich ist;)
Arjan Tijms
2
@Kawy: Der Methodenname kann bei Verwendung im actionListenerAttribut frei gewählt werden und muss es publicauch sein. Der processActionName ist nur obligatorisch, wenn Sie ihn verwenden <f:actionListener type>, einfach weil der Typ eine ActionListenerSchnittstelle implementieren muss , für die genau dieser Methodenname processActiondefiniert ist.
BalusC
2
@Muhammed: Ajax Action Listener wird vor allen regulären Action Listenern aufgerufen. Beachten Sie, dass Sie bei Verwendung <f:ajax>von Befehlskomponenten das actionAttribut für Geschäftsaktionen bevorzugen . ZB <h:commandButton action="#{bean.businessAction}"><f:ajax/></h:commandButton>.
BalusC
47

Wie BalusC angedeutet hat, actionListenerwerden standardmäßig Ausnahmen verschluckt, aber in JSF 2.0 steckt noch ein bisschen mehr dahinter . Das heißt, es nicht nur Schwalben und Protokolle, aber tatsächlich veröffentlicht die Ausnahme.

Dies geschieht durch einen Anruf wie folgt:

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

Der Standard-Listener für dieses Ereignis ist der ExceptionHandlerfür Mojarra festgelegte com.sun.faces.context.ExceptionHandlerImpl. Diese Implementierung löst grundsätzlich alle Ausnahmen erneut aus, es sei denn, es handelt sich um eine AbortProcessingException, die protokolliert wird. ActionListener schließen die vom Clientcode ausgelöste Ausnahme in eine solche AbortProcessingException ein, in der erklärt wird, warum diese immer protokolliert werden.

Dies ExceptionHandlerkann jedoch in der Datei features-config.xml durch eine benutzerdefinierte Implementierung ersetzt werden:

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

Anstatt global zuzuhören, kann eine einzelne Bean diese Ereignisse auch abhören. Das Folgende ist ein Proof of Concept dafür:

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

}

(Beachten Sie, dass man Hörer normalerweise nicht so codieren sollte, dies dient nur zu Demonstrationszwecken!)

Nennen Sie dies von einem Facelet wie folgt:

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

Es wird eine Fehlerseite angezeigt.

Arjan Tijms
quelle
43

ActionListener wird zuerst ausgelöst, mit einer Option zum Ändern der Antwort, bevor Action aufgerufen wird und den Speicherort der nächsten Seite bestimmt.

Wenn Sie mehrere Schaltflächen auf derselben Seite haben, die an dieselbe Stelle gehen sollen, aber leicht unterschiedliche Aufgaben ausführen, können Sie für jede Schaltfläche dieselbe Aktion verwenden, jedoch einen anderen ActionListener verwenden, um geringfügig unterschiedliche Funktionen zu verarbeiten.

Hier ist ein Link, der die Beziehung beschreibt:

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

Erick Robertson
quelle
3
plus eins, Die fetten Buchstaben sagen fast alles.
Shirgill Farhan
0

TL; DR :

Die ActionListeners (es können mehrere sein) werden in der Reihenfolge ausgeführt, in der sie VOR dem registriert wurdenaction

Lange Antwort :

Ein Unternehmen actionruft normalerweise einen EJB-Service auf und legt bei Bedarf auch das Endergebnis fest und / oder navigiert zu einer anderen Ansicht, wenn dies nicht das ist, was Sie tun, und actionListenerist besser geeignet, dh wenn der Benutzer mit den Komponenten interagiert, wie h:commandButtonoder h:linkkönnen sie Sie müssen den Namen der verwalteten Bean-Methode im actionListenerAttribut einer UI-Komponente übergeben oder eine ActionListenerSchnittstelle implementieren und den Namen der Implementierungsklasse an das actionListenerAttribut einer UI-Komponente übergeben.

Yehuda Schwartz
quelle