Diferenças entre action e actionListener

393

Qual é a diferença entre actionand actionListener, e quando devo usar actionversus actionListener?

Murat Güzel
fonte

Respostas:

583

actionListener

Use actionListenerse você deseja obter um gancho antes que a ação real do negócio seja executada, por exemplo, registrá-la e / ou definir uma propriedade adicional (por <f:setPropertyActionListener>) e / ou ter acesso ao componente que chamou a ação (disponível por ActionEventargumento). Portanto, puramente para fins de preparação antes que a ação real do negócio seja invocada.

O actionListenermétodo possui por padrão a seguinte assinatura:

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

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

E deve ser declarado da seguinte forma, sem nenhum parênteses de método:

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

Observe que você não pode passar argumentos adicionais pelo EL 2.2. No entanto, você pode substituir ActionEventcompletamente o argumento passando e especificando argumentos personalizados. Os seguintes exemplos são 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) {}

Observe a importância dos parênteses na expressão do método sem argumento. Se eles estivessem ausentes, o JSF ainda esperaria um método com ActionEventargumento.

Se você usa o EL 2.2+, pode declarar vários métodos de ouvinte de ação por meio 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() {}

Observe a importância dos parênteses no bindingatributo Se eles estivessem ausentes, o EL confundiria um a javax.el.PropertyNotFoundException: Property 'actionListener1' not found on type com.example.Bean, porque o bindingatributo é por padrão interpretado como uma expressão de valor, não como uma expressão de método. A adição de parênteses do estilo EL 2.2+ transforma transparentemente uma expressão de valor em uma expressão de método. Consulte também ao Por que eu sou capaz de vincular <f: actionListener> a um método arbitrário se ele não é suportado pelo JSF?


açao

Use actionse você deseja executar uma ação comercial e, se necessário, manipular a navegação. O actionmétodo pode (portanto, não deve) retornar um Stringque será usado como resultado do caso de navegação (a exibição de destino). Um valor de retorno nullou voidpermitirá retornar à mesma página e manter vivo o escopo da visualização atual. Um valor de retorno de uma cadeia vazia ou o mesmo ID da visualização também retornará à mesma página, mas recriará o escopo da visualização e, portanto, destruirá quaisquer beans com escopo da visualização atualmente ativos e, se aplicável, os recriará.

O actionmétodo pode ser válido MethodExpression, também aquele que usa argumentos do EL 2.2, como abaixo:

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

Com este método:

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

Observe que quando seu método de ação retorna apenas uma sequência, você também pode especificar exatamente essa sequência no actionatributo Portanto, isso é totalmente desajeitado:

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

Com esse método sem sentido, retornando uma string codificada:

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

Em vez disso, basta colocar essa string codificada diretamente no atributo:

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

Observe que isso, por sua vez, indica um design incorreto: navegar pelo POST. Este não é um usuário nem um SEO amigável. Isso tudo é explicado em Quando devo usar h: outputLink em vez de h: commandLink? e deve ser resolvido como

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

Veja também Como navegar no JSF? Como fazer o URL refletir a página atual (e não a anterior) .


ouvinte f: ajax

Desde o JSF 2.x existe uma terceira maneira, o <f:ajax listener>.

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

O ajaxListenermétodo possui por padrão a seguinte assinatura:

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

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

Em Mojarra, o AjaxBehaviorEventargumento é opcional, abaixo funciona tão bem.

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

Mas no MyFaces, seria lançado a MethodNotFoundException. Abaixo, funciona nas duas implementações JSF quando você deseja omitir o argumento.

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

Os ouvintes do Ajax não são realmente úteis nos componentes de comando. Eles são mais úteis na entrada e selecione componentes <h:inputXxx>/ <h:selectXxx>. Nos componentes de comando, apenas atenha-se ae action/ ou actionListenerpara maior clareza e melhor código de auto-documentação. Além disso, actionListenero f:ajax listenernão suporta o retorno de um resultado de navegação.

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

Para obter explicações executee renderatributos, vá para Noções básicas sobre processo / atualização do PrimeFaces e atributos de execução / renderização do JSF f: ajax .


Ordem de invocação

Os actionListenersempre são invocados antes doaction na mesma ordem em que foram declarados na visualização e anexados ao componente. O f:ajax listenersempre é chamado antes de qualquer ouvinte de ação. Então, o seguinte exemplo:

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

Irá invocar os métodos na seguinte ordem:

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

Manipulação de exceção

O actionListenersuporta uma exceção especial:AbortProcessingException . Se essa exceção for lançada de um actionListenermétodo, o JSF ignorará todos os listeners de ação restantes e o método de ação e continuará a renderizar a resposta diretamente. Você não verá uma página de erro / exceção, mas o JSF a registrará. Isso também será implicitamente feito sempre que qualquer outra exceção for lançada de um actionListener. Portanto, se você pretende bloquear a página por uma página de erro como resultado de uma exceção comercial, você definitivamente deve executar o trabalho no actionmétodo

Se o único motivo para usar um actionListeneré ter um voidmétodo retornando à mesma página, isso é ruim. Os actionmétodos também podem retornar perfeitamente void, ao contrário do que alguns IDEs permitem que você acredite através da validação EL. Observe que o exemplos de apresentação PrimeFaces estão repletos de esse tipo de actionListeners em todos os lugares. Isso está realmente errado. Não use isso como desculpa para também fazer isso sozinho.

Em solicitações ajax, no entanto, é necessário um manipulador de exceção especial. Isso independentemente de você usar ou não o listeneratributo <f:ajax>. Para obter explicações e um exemplo, vá para Manipulação de exceções em solicitações JSF ajax .

BalusC
fonte
11
Você está certo de que as exceções nos actionListeners são engolidas por padrão, mas no JSF 2.0 esse comportamento pode ser alterado. Veja minha resposta abaixo para obter detalhes.
Arjan Tijms
3
@arjan: você está certo de que o JSF 2.0 permite alterar o tratamento padrão das exceções geradas actionListener, mas isso ainda não é uma boa desculpa para abusar actionListenerde ações de negócios .
precisa
11
De fato, as ações de negócios estão no "fluxo" principal do ciclo de solicitação / resposta e apenas o actioncorresponde a isso. actionListeneré para coisas secundárias. Apenas queria esclarecer que as exceções de actionListeners podem ser propagadas se assim for exigido;)
Arjan Tijms
2
@ Kawy: o nome do método é livre de escolha quando usado no actionListeneratributo e também deve ser public. O processActionnome é obrigatório apenas quando você está usando <f:actionListener type>, simplesmente porque o tipo precisa implementar uma ActionListenerinterface que tenha exatamente esse nome de método processActiondefinido.
precisa saber é o seguinte
2
@ Muhammed: o ouvinte de ação do ajax é chamado antes de todos os ouvintes de ação regular. Observe que, mesmo ao usar <f:ajax>, no caso de componentes de comando, você prefere usar o actionatributo para ações de negócios. Por exemplo <h:commandButton action="#{bean.businessAction}"><f:ajax/></h:commandButton>.
precisa saber é o seguinte
47

Como o BalusC indicou, o actionListenerpadrão engole exceções, mas no JSF 2.0 há um pouco mais disso. Ou seja, ele não apenas engole e registra, mas na verdade publica a exceção.

Isso acontece através de uma chamada como esta:

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

O ouvinte padrão para este evento é o ExceptionHandlerque para Mojarra está definido comocom.sun.faces.context.ExceptionHandlerImpl . Essa implementação basicamente relançará qualquer exceção, exceto quando se trata de uma AbortProcessingException, que é registrada. Os ActionListeners quebram a exceção lançada pelo código do cliente em uma AbortProcessingException, que explica por que eles sempre são registrados.

No ExceptionHandlerentanto, isso pode ser substituído no faces-config.xml com uma implementação customizada:

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

Em vez de ouvir globalmente, um único bean também pode ouvir esses eventos. A seguir, é uma prova de conceito disso:

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

}

(note que não é assim que normalmente se deve codificar ouvintes, isso é apenas para fins de demonstração!)

Chamando isso de um Facelet assim:

<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á na exibição de uma página de erro.

Arjan Tijms
fonte
43

O ActionListener é acionado primeiro, com uma opção para modificar a resposta, antes que o Action seja chamado e determine o local da próxima página.

Se houver vários botões na mesma página que devem ir para o mesmo lugar, mas executar ações ligeiramente diferentes, você poderá usar a mesma ação para cada botão, mas usar um ActionListener diferente para lidar com funcionalidades ligeiramente diferentes.

Aqui está um link que descreve o relacionamento:

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

Erick Robertson
fonte
3
mais um, as letras em negrito dizem quase tudo.
Shirgill Farhan
0

TL; DR :

Os ActionListeners (podem existir múltiplos) são executados na ordem em que foram registrados ANTES doaction

Resposta longa :

Um negócio actionnormalmente invoca um serviço EJB e, se necessário também define o resultado final e / ou navega para uma vista diferente se isso não é o que você está fazendo um actionListeneré isto é mais apropriado para quando o usuário interage com os componentes, tais como h:commandButtonou h:linko que podem seja tratado passando o nome do método do bean gerenciado no actionListeneratributo de um Componente da UI ou para implementar uma ActionListenerinterface e passe o nome da classe de implementação ao actionListeneratributo de um Componente da UI.

Yehuda Schwartz
fonte