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
Novo no JSF, pode dar um exemplo do uso?
Editei o post e gravei uma vídeo aula explicando como se faz isso.
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.
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.
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.
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.
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
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.
Excellent..!!
Thanks for your information, Leandro.
I’m From Ecuador.
Parabéns!!!!
Muito útil o seu blog.
eh possivel pegar excessoes geradas pelo hibernate? do tipo valor duplicado ou vinculado a outra tabela?
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
Seu post ajudou demais cara.
Vlws
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.
Boa noite Italo, ao que tudo indica está faltando bibliotecas no seu projeto “java.lang.ClassNotFoundException”. Tem um vídeo no Youtube(https://www.youtube.com/watch?v=fbcLOUJmS9k) complementando este post e se ainda assim não resolver pegue o projeto completo no github e se baseie nele (https://github.com/leandrocgsi/semeru_jsf_maven).
Boa sorte e bons estudos
Como se faz para tratar sessão expirada utlizando o codigo acima?
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!
Muito bom esse post!
Obrigado!
Tutorial show de bola! Contudo o mesmo não funciona quando utilizamos o ajax do primefaces.
Como resolver? ou que fazer?
Olá João. O post foi publicado em 2012 de lá pra cá muita coisa mudou, vou revisar esse ponto e fazer uma repostagem.