Różnice między akcją a listą akcji

393

Jaka jest różnica między actioni actionListener, i kiedy należy użyć actionkontra actionListener?

Murat Güzel
źródło

Odpowiedzi:

583

actionListener

Użyj, actionListenerjeśli chcesz mieć hak przed wykonaniem prawdziwej akcji biznesowej, np. Aby ją zarejestrować i / lub ustawić dodatkową właściwość (by <f:setPropertyActionListener>) i / lub mieć dostęp do komponentu, który wywołał akcję (który jest dostępny przez ActionEventargument). Zatem wyłącznie w celu przygotowania celów przed wywołaniem prawdziwej akcji biznesowej.

actionListenerMetoda ma domyślnie następujący podpis:

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

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

I powinien być zadeklarowany w następujący sposób, bez nawiasów metod:

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

Zauważ, że nie możesz przekazać dodatkowych argumentów przez EL 2.2. Możesz jednak ActionEventcałkowicie zastąpić argument, przekazując i określając niestandardowe argumenty. Poprawne są następujące przykłady:

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

Zwróć uwagę na znaczenie nawiasów w wyrażeniu metody bez argumentów. Gdyby ich nie było, JSF nadal oczekiwałby metody z ActionEventargumentami.

Jeśli korzystasz z EL 2.2+, możesz zadeklarować wiele metod nasłuchiwania akcji za pośrednictwem <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() {}

Zwróć uwagę na znaczenie nawiasów w bindingatrybucie. Gdyby były nieobecne, EL myląco rzuciłoby a javax.el.PropertyNotFoundException: Property 'actionListener1' not found on type com.example.Bean, ponieważ bindingatrybut jest domyślnie interpretowany jako wyrażenie wartości, a nie jako wyrażenie metody. Dodanie nawiasów w stylu EL 2.2+ powoduje, że wyrażenie wartości staje się wyrażeniem metody. Zobacz także: Dlaczego mogę powiązać <f: actionListener> z dowolną metodą, jeśli nie jest obsługiwana przez JSF?


akcja

Użyj, actionjeśli chcesz wykonać akcję biznesową i, jeśli to konieczne, obsługiwać nawigację. actionSposób może (a więc nie musi) zwracają String, które będą stosowane jako wynik przypadku nawigacji (widok z góry docelowy). Zwracana wartość nulllub voidumożliwia powrót do tej samej strony i utrzymanie bieżącego zakresu widoku. Zwracana wartość pustego ciągu lub tego samego identyfikatora widoku również powróci na tę samą stronę, ale odtworzy zakres widoku, a tym samym zniszczy wszystkie aktywne komponenty bean o zasięgu widoku i, jeśli to konieczne, odtworzy je.

actionMetoda może być jakieś ważne MethodExpression, także te, które wykorzystuje EL 2.2 argumenty takie jak poniżej:

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

Dzięki tej metodzie:

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

Zauważ, że gdy metoda akcji zwraca tylko ciąg znaków, możesz również dokładnie określić dokładnie ten ciąg w actionatrybucie. Jest to zatem całkowicie niezdarne:

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

Za pomocą tej bezsensownej metody zwracania zakodowanego ciągu:

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

Zamiast tego po prostu umieść ten zakodowany ciąg bezpośrednio w atrybucie:

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

Pamiętaj, że to z kolei oznacza zły projekt: nawigacja POST. To nie jest przyjazny dla użytkownika ani SEO. Wszystko to wyjaśniono w rozdziale Kiedy powinienem używać h: outputLink zamiast h: commandLink? i ma być rozwiązany jako

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

Zobacz także Jak nawigować w JSF? Jak sprawić, by adres URL odzwierciedlał bieżącą stronę (a nie poprzednią) .


f: odbiornik ajax

Od JSF 2.x istnieje trzeci sposób, czyli <f:ajax listener>.

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

ajaxListenerMetoda ma domyślnie następujący podpis:

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

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

W Mojarra AjaxBehaviorEventargument jest opcjonalny, poniżej działa tak samo dobrze.

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

Ale w MyFaces rzuciłoby MethodNotFoundException. Poniżej działa w obu implementacjach JSF, jeśli chcesz pominąć argument.

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

Detektory Ajax nie są tak naprawdę przydatne w komponentach poleceń. Są bardziej przydatne przy wprowadzaniu i wybieraniu komponentów <h:inputXxx>/ <h:selectXxx>. W komponentach poleceń po prostu trzymaj się actioni / lub actionListenerw celu uzyskania przejrzystości i lepszego samodokumentowania kodu. Ponadto, podobnie jak actionListener, f:ajax listenernie obsługuje zwracania wyniku nawigacji.

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

Aby uzyskać wyjaśnienie executei renderatrybuty, przejdź do Zrozumienie procesu / aktualizacji PrimeFaces i JSF f: ajax atrybuty wykonania / renderowania .


Kolejność wywoływania

Do actionListeners zawsze są wywoływane przedaction w tej samej kolejności, jak są one zostały uznane w widoku i dołączone do komponentu. f:ajax listenerZawsze jest wywoływana przed każdym słuchaczem działania. Tak więc następujący przykład:

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

Wywoła metody w następującej kolejności:

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

Obsługa wyjątków

actionListenerObsługuje specjalny wyjątek: AbortProcessingException. Jeśli ten wyjątek zostanie zgłoszony z actionListenermetody, JSF pominie pozostałe nasłuchiwania akcji i metodę akcji i przejdzie bezpośrednio do renderowania odpowiedzi. Nie zobaczysz strony błędu / wyjątku, jednak JSF ją zarejestruje. Będzie to również domyślnie wykonane za każdym razem, gdy zostanie zgłoszony jakikolwiek inny wyjątek od actionListener. Jeśli więc zamierzasz zablokować stronę za pomocą strony błędu w wyniku wyjątku biznesowego, zdecydowanie powinieneś wykonać zadanie w actionmetodzie.

Jeśli jedynym powodem użycia an actionListenerjest voidpowrót metody do tej samej strony, to źle. Te actionmetody mogą doskonale również powrócić void, przeciwnie do tego, co niektórzy IDE pozwalają wierzyć poprzez walidację EL. Zauważ, że przykłady prezentacji PrimeFaces są zaśmiecone tego rodzaju literamiactionListener wszędzie. To jest rzeczywiście złe. Nie używaj tego jako pretekstu, aby zrobić to sam.

Jednak w żądaniach ajax potrzebna jest specjalna procedura obsługi wyjątków. Jest to niezależne od tego, czy używasz listeneratrybutu, <f:ajax>czy nie. Aby uzyskać wyjaśnienie i przykład, przejdź do Obsługa wyjątków w żądaniach JSF ajax .

BalusC
źródło
1
Masz rację, że wyjątki w działaniu Listery są domyślnie połykane, ale w JSF 2.0 to zachowanie można zmienić. Zobacz moją odpowiedź poniżej, aby uzyskać szczegółowe informacje.
Arjan Tijms,
3
@arjan: masz rację, że JSF 2.0 pozwala zmienić domyślną obsługę actionListenerzgłaszanych wyjątków , ale nadal nie stanowi to dobrej wymówki do nadużyć actionListenerw działaniach biznesowych .
BalusC,
1
Rzeczywiście, działania biznesowe znajdują się w głównym „cyklu” cyklu żądania / odpowiedzi i tylko to actionodpowiada. actionListenerjest na drugorzędne rzeczy. Chciałem tylko wyjaśnić, że w actionListenerrazie potrzeby można propagować wyjątki od s;)
Arjan Tijms,
2
@Kawy: nazwa metody jest dowolna, jeśli zostanie użyta w actionListeneratrybucie, i tak też musi być public. processActionNazwa jest obowiązkowa tylko gdy używasz <f:actionListener type>, po prostu dlatego, że typ musi implementować ActionListenerinterfejs, który ma dokładnie to nazwa metody processActionzdefiniowana.
BalusC
2
@Muhammed: nasłuchiwanie akcji ajax jest wywoływane przed wszystkimi odbiornikami regularnych akcji. Pamiętaj, że nawet podczas używania <f:ajax>wolisz w przypadku komponentów poleceń używać actionatrybutu do działań biznesowych. Np <h:commandButton action="#{bean.businessAction}"><f:ajax/></h:commandButton>.
BalusC
47

Jak wskazał BalusC, actionListenerdomyślnie połyka wyjątki, ale w JSF 2.0 jest coś więcej. Mianowicie, nie tylko połyka i rejestruje, ale faktycznie publikuje wyjątek.

Dzieje się tak przez takie połączenie:

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

Domyślnym detektorem tego zdarzenia jest ustawiony ExceptionHandlerdla Mojarry com.sun.faces.context.ExceptionHandlerImpl. Ta implementacja zasadniczo zwróci każdy wyjątek, z wyjątkiem przypadków, gdy dotyczy wyjątku AbortProcessingException, który jest rejestrowany. ActionListeners zawijają wyjątek zgłaszany przez kod klienta w takim AbortProcessingException, który wyjaśnia, dlaczego są one zawsze rejestrowane.

To ExceptionHandlermoże być zastąpiony jednak w twarze-config.xml z realizacji niestandardowych:

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

Zamiast słuchać na całym świecie, jedna fasola może również słuchać tych wydarzeń. Oto dowód tego:

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

}

(uwaga: nie należy tak normalnie kodować słuchaczy, służy to wyłącznie do celów demonstracyjnych!)

Wywoływanie tego z Facelet takiego:

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

Powoduje wyświetlenie strony błędu.

Arjan Tijms
źródło
43

ActionListener jest uruchamiany jako pierwszy, z opcją modyfikacji odpowiedzi, zanim Action zostanie wywołana i określi lokalizację następnej strony.

Jeśli masz wiele przycisków na tej samej stronie, które powinny iść w to samo miejsce, ale robisz nieco inne rzeczy, możesz użyć tej samej akcji dla każdego przycisku, ale użyć innej ActionListener do obsługi nieco innej funkcjonalności.

Oto link opisujący relację:

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

Erick Robertson
źródło
3
plus jeden, pogrubione litery mówią prawie wszystko.
Shirgill Farhan
0

TL; DR :

W ActionListeners (może być wielokrotnością) wykonać w kolejności ich zarejestrowanych przedaction

Długa odpowiedź :

Firma actionzazwyczaj wywołuje usługę EJB i w razie potrzeby ustawia ostateczny wynik i / lub przechodzi do innego widoku, jeśli nie to, co robisz, actionListenerjest bardziej odpowiednie, tj. Gdy użytkownik wchodzi w interakcję z komponentami, takimi jak h:commandButtonlub h:linkmoże być obsługiwany przez przekazanie nazwy metody zarządzanego actionListenerkomponentu bean w atrybucie komponentu interfejsu użytkownika lub w celu zaimplementowania ActionListenerinterfejsu i przekazania nazwy klasy implementacji do actionListeneratrybutu komponentu interfejsu użytkownika.

Yehuda Schwartz
źródło