Manipulando de Exceções no JavaServer Faces (JSF) 2.x

Uma das funcionalidades mais simples e legais introduzidas pela especificação 2.0 do JSF é a possibilidade de se criar um manipulador global de exceções. Trechos de código como esse aqui abaixo tratando as exceções previstas uma a uma no web.xml se tornaram desnecessários.

<error-page>
  <error-code>404</error-code>
  <location>/404.xhtml</location>
</error-page>

Ao invés da solução acima, o JavaServer Faces nos permite implementar um manipulador global de exceções de forma relativamente simples para todas as exceções que podem ocorrer na aplicação. Para isso, você precisa apenas de criar duas classes que estendam as classes:
ExceptionHandlerWrapper – Que fornece uma implementação simplificada da ExceptionHandler permitindo por exemplo que os desenvolvedores possam fornecer um comportamento especializado para uma instância ExceptionHandler.
ExceptionHandlerFactory – Essa classe por sua vez atua como uma Factory responsável por criar e retornar, quando necessário, uma nova instância de ExceptionHandler.
Por fim você precisa apenas algumas linhas ao arquivo “faces.config.xml” responsáveis por registrar a classe de tratamento no FacesServlet.

<factory>
  <exception-handler-factory>
     br.com.semeru.exceptions.CustomExceptionHandlerFactory
  </exception-handler-factory>
</factory>

No trecho de código abaixo temos a classe “CustomExceptionHandlerFactory” que é responsável por fabricar uma instância da classe “CustomExceptionHandler” que é responsável por capturar e tratar a exceção.

package br.com.semeru.exceptions;

import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerFactory;

public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory {
    private ExceptionHandlerFactory parent;

    public CustomExceptionHandlerFactory(ExceptionHandlerFactory parent) {
        this.parent = parent;
    }

    @Override
    public ExceptionHandler getExceptionHandler() {
        ExceptionHandler handler = new CustomExceptionHandler(parent.getExceptionHandler());
        return handler;
    }

}

Já no trecho abaixo temos a classe “CustomExceptionHandler” que pode tratar a exceção da forma que você julgar mais apropriada. Você pode simplesmente imprimir a StackTrace e retornar uma página de erros, ou tratar as exceções que mais ocorrem e retornar uma página personalizada para cada uma delas. Outra coisa que pode ser interessante é enviar a StackTrace via e-mail para a equipe de desenvolvimento.

package br.com.semeru.exceptions;

//import java.io.PrintWriter;
//import java.io.StringWriter;
import java.util.Iterator;
import java.util.Map;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.application.NavigationHandler;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerWrapper;
import javax.faces.context.FacesContext;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.ExceptionQueuedEventContext;

//Inicialmente devemos implementar a classe CustomExceptionHandler que extende a classe ExceptionHandlerWrapper
public class CustomExceptionHandler extends ExceptionHandlerWrapper {

    private ExceptionHandler wrapped;

    //Obtém uma instância do FacesContext
    final FacesContext facesContext = FacesContext.getCurrentInstance();

    //Obtém um mapa do FacesContext
    final Map requestMap = facesContext.getExternalContext().getRequestMap();

    //Obtém o estado atual da navegação entre páginas do JSF
    final NavigationHandler navigationHandler = facesContext.getApplication().getNavigationHandler();

    //Declara o construtor que recebe uma exceptio do tipo ExceptionHandler como parâmetro
    CustomExceptionHandler(ExceptionHandler exception) {
        this.wrapped = exception;
    }

    //Sobrescreve o método ExceptionHandler que retorna a "pilha" de exceções
    @Override
    public ExceptionHandler getWrapped() {
        return wrapped;
    }

    //Sobrescreve o método handle que é responsável por manipular as exceções do JSF
    @Override
    public void handle() throws FacesException {

        final Iterator iterator = getUnhandledExceptionQueuedEvents().iterator();
        while (iterator.hasNext()) {
            ExceptionQueuedEvent event = iterator.next();
            ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();

            // Recupera a exceção do contexto
            Throwable exception = context.getException();

            // Aqui tentamos tratar a exeção
            try {

//                // Aqui você poderia por exemploinstanciar as classes StringWriter e PrintWriter
//                StringWriter stringWriter = new StringWriter();
//                // PrintWriter printWriter = new PrintWriter(stringWriter);
//                // exception.printStackTrace(printWriter);
//                // Por fim você pode converter a pilha de exceções em uma String
//                String message = stringWriter.toString();
//
//                // Aqui você poderia enviar um email com a StackTrace
//                // em anexo para a equipe de desenvolvimento
//
//                // e depois imprimir a stacktrace no log
//                exception.printStackTrace();

                // Coloca uma mensagem de exceção no mapa da request
                requestMap.put("exceptionMessage", exception.getMessage());

                // Avisa o usuário do erro
                FacesContext.getCurrentInstance().addMessage(null, new FacesMessage
                    (FacesMessage.SEVERITY_ERROR, "O sistema se recuperou de um erro inesperado.", ""));

                // Tranquiliza o usuário para que ele continue usando o sistema
                FacesContext.getCurrentInstance().addMessage(null, new FacesMessage
                    (FacesMessage.SEVERITY_INFO, "Você pode continuar usando o sistema normalmente!", ""));

                // Seta a navegação para uma página padrão.
                navigationHandler.handleNavigation(facesContext, null, "/restrict/home.faces");

                // Renderiza a pagina de erro e exibe as mensagens
                facesContext.renderResponse();
            } finally {
                // Remove a exeção da fila
                iterator.remove();
            }
        }
        // Manipula o erro
        getWrapped().handle();
    }
}

No bloco try-finally você pode converter o Throwable em qualquer exceção específica e dar um tratamento especial a cada uma delas. Um simples problema de ViewExpiredException, por exemplo, pode ser redirecionado para uma página informando que a “sessão expirou” já, no caso de uma exceção do tipo NullPointerException podemos usar uma página padrão apenas dizendo algo como “Ocorreu um erro inesperado, por favor, tente novamente mais tarde.”
Um detalhe muito importante a se lembrar é que ao lidar com exceções em desenvolvimento web, você deve garantir que não está compartilhando informações indesejadas e/ou sensíveis ao retornar o erro para a página web.

Bom é isso e espero que gostem do post.

Editado para responder ao comentário do André

Olá gravei uma vídeo aula explicando de forma clara como adotar o exception handler em um projeto JSF.

Treinamentos relacionados com este post














23 thoughts to “Manipulando de Exceções no JavaServer Faces (JSF) 2.x”

  1. Parabéns pela postagem.

    Amigo, eu fiz no meu e colocando o debug, ele consegue pegar o erro, porém ele ñ lança as mensagens e nem vai pra página que eu o mando redirecionar em caso de erro. Nota: A página tem growl.

  2. Amigo, se quiser, não precisa postar essa postagem minha mais, nem essa nem a #3 que eu fiz, eu dei umas duas olhadas e vi qual era meu erro, eu que tinha escrito uma parte do código errado e não reparei no erro.

    Mas parabéns pela postagem. Ótima informação e didática.

  3. Amigo. Boa tarde.

    Como eu faria então para tratar tipos diferentes de erro, por exemplo, se o erro for javax.faces.application.ViewExpiredException ele exibe uma página, porém se for qualquer outro, ele exibiria outra página, ou uma página padrão, digamos assim. Eu olhei o objeto context do método handle da classe CustomExceptionHandle e o mais próximo que posso encontrar é uma váriavel com isso tudo: javax.faces.application.ViewExpiredException: viewId:/pages/mapa_andradina.jsf – A exibição de /pages/mapa_andradina.jsf não pôde ser restaurada.

    Eu poderia fazer um split por “:”, separar e pegar o primeiro elemento, mas queria saber de ti, se tem uma forma mais elegante para que eu execute esta tarifa.

    Desde já, muito obrigado mesmo.

  4. Olá Jonathas, as coisas estão meio corridas e talvez você já tenha resolvido isso. Na verdade eu ainda não precisei fazer um tratamento personalizado de exceções, geralmente eu gosto de usar essa estratégia para capturar as exceptions e mandar por e-mail assim a equipe de desenvolvimento pode analisar o problema e ir melhorando nas novas versões. Acredito que você consiga capturar o tipo da exception a partir do ExceptionQueuedEventContext.

    ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();

    Throwable exception = context.getException();

    Verifica se tem algum método que lhe retorna o tipo e trata uma a uma da forma que achar melhor, redirecionando pra onde você achar mais relevanete.

    Boa sorte.

  5. Oi Leandro, estou tentando tratar a exception mas na linha 45 do CustomExceptionHandler (final Iterator iterator = getUnhandledExceptionQueuedEvents().iterator() ) o iterator esta null ,
    oque pode ser? o sistema nao lancou nenhuma exception ?
    Agradeco qualquer dica
    Obrigado

    1. Bom é só copiar e colar este código, não tem erro. O que pode estar acontecendo é você estar usando uma outra versão diferente da 2.2X.

    1. Hibernate: delete from inforpratica.public.cd_grupopreco where id_grupopreco=?
      Out 31, 2013 4:33:18 PM org.hibernate.util.JDBCExceptionReporter logExceptions
      WARNING: SQL Error: 0, SQLState: 23503
      Out 31, 2013 4:33:18 PM org.hibernate.util.JDBCExceptionReporter logExceptions
      SEVERE: Entrada em lote 0 delete from inforpratica.public.cd_grupopreco where id_grupopreco=12111 foi abortada. Chame getNextException para ver a causa.
      Out 31, 2013 4:33:18 PM org.hibernate.util.JDBCExceptionReporter logExceptions
      WARNING: SQL Error: 0, SQLState: 23503
      Out 31, 2013 4:33:18 PM org.hibernate.util.JDBCExceptionReporter logExceptions
      SEVERE: ERROR: update or delete on table “cd_grupopreco” violates foreign key constraint “fk_grupopreco_produto” on table “cd_produto”
      Detalhe: Key (id_grupopreco)=(12111) is still referenced from table “cd_produto”.
      Out 31, 2013 4:33:18 PM org.hibernate.event.def.AbstractFlushingEventListener performExecutions
      SEVERE: Could not synchronize database state with session
      org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update

  6. Boa tarde Leandro, estou com um problema na exceção.
    Copiei o seu código acima, mas quando tenho uma exceção de javax.faces.FacesException, java.lang.IllegalStateException e java.lang.ClassNotFoundException o sistema não está fazendo o tratamento da exceção e também não está redirecionando para tela de erro.
    Gostaria de saber como eu resolvo esse problema.
    Desde já grato.

    1. Com sessão expirada o máximo que você pode fazer é redirecionar o usuário para a tela de login. Seria algo similar ao que é feito na linha 81. Tente algo como “navigationHandler.handleNavigation(facesContext, null, “/restrict/login.faces”);”. Boa sorte e bons estudos!

    1. Olá João. O post foi publicado em 2012 de lá pra cá muita coisa mudou, vou revisar esse ponto e fazer uma repostagem.

  7. Excelente tutorial, muito bem explicado.

    Apenas uma dúvida, eu possuo no meu web.xml um tratamento para sessão expirada, utilizando essa técnica apresentada por voce não respeita esse tratamento. O que eu poderia fazer?

    Obrigado

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *