Lidando com exceptions dentro do Spring MVC

Em qualquer projeto que participamos, uma das única coisas que temos certeza, é que uma exception vai acontecer. Tanto é que a especificação de Servlets já define uma maneira formal de fazer o tratamento das básicas.

  <error-page>
  	<error-code>404</error-code>
  	<location>/WEB-INF/views/errors/404.jsp</location>
  </error-page>

  <error-page>
  	<exception-type>java.lang.Exception</exception-type>
  	<location>/WEB-INF/views/errors/500.jsp</location>
  </error-page>

Só que, em geral, quase todo framework MVC que nós usamos já fornece um mecanismo alternativo de tratamento para as exceptions. Com o Spring MVC não podia ser diferente. E o legal é que as possibilidades oferecidas por ele vão além das fornecidas pela especificação. Por exemplo, no SetupMyProject quando uma pessoa tenta baixar um projeto com um token que não existe, é lançada uma exception do tipo NotFoundStatusException, indicando justamente o problema. Em um código convencional, exceptions apenas representam problemas, mas nesse caso também representa um status, que no caso é o 404.  Talvez a maneira mais direta de tratar ela seria da seguinte forma.

	public HttpEntity<byte[]> download(String token,
		HttpServletResponse response) {

		try {
                   //logica para buscar o setup e gerar o projeto
		} catch (NotFoundStatusException e) {
			response.setStatus(404);
            ModelAndView notFound = new ModelAndView("errors/404");
            notFound.addObject("message","the setup does not exist");
            return notFound;
	}

Só que esse código já nos obriga a receber um parâmetro a mais e adicionar um try/catch no nosso método. Caso deixemos ela estourar, vamos ter que mapear no web.xml, só que aí perdemos a chance de tratar como 404. E nesse caso, a página de 404 teria que ter uma mensagem customizada, como faríamos? Para este tipo de situação o Spring MVC já provê um saída.

    public HttpEntity<byte[]> download(String token,HttpServletResponse response) {
          //logica para buscar o setup e gerar o projeto
          return download;
    }

    public ModelAndView handleNotFoundStatusException
	 (NotFoundStatusException ex) {
	 ModelAndView modelAndView = modelAndViewForTokenExceptions(ex);
	 pageMessages.info("token.not_found",
	    "You need to generate a project before download", modelAndView);
	 return modelAndView;
     }

Perceba que basta você anotar o método com @ExceptionHandler e aí você pode receber a exception específica como argumento.  Aqui você já pode adicionar qualquer mensagem que você queira e direcionar para página que você necessite. Só que ainda podemos ir além, podemos informar ao Spring MVC que na verdade, essa exception deve gerar um status 404.

     @ResponseStatus(value=HttpStatus.NOT_FOUND)
     public class NotFoundStatusException extends RuntimeException
		implements RequestedSetupTokenAwareException {

           ...
     }

Você pode usar essa estratégia em qualquer controller seu! Um último ponto é: como você vai tratar aquelas exceptions que você não espera? Da maneira atual, teríamos que colocar um @ExceptionHandler em cada um dos controllers do sistema ou apelar para o modelo de aspectos suportado pelo framework. Para contornar isso, desde a versão 3.2, o Spring MVC possui uma annotation para indicar que certa classe concentra tratamentos globais relativos a um conjunto de controllers.

        @ControllerAdvice
	public class GlobalExceptionHandler {

		@Autowired
		private AccessEnvironment env;
		@Autowired
		private MailSender mailer;
		@Autowired
		private ThreadPoolTaskExecutor executor;

		private Logger logger = LoggerFactory
				.getLogger(GlobalExceptionHandler.class);

		@ExceptionHandler(value = Exception.class)
		public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e)
				throws Exception {

			SimpleMailMessage email = createBaseEmail();
			email.setText(ExceptionUtils.getStackTrace(e));

			executor.execute(() -> {
				logger.error("Enviando mensagem de erro");
				mailer.send(email);
			});

			return new ModelAndView("errors/500");
		}

Você pode usar a annotation @ControllerAdvice sempre que precisar concentrar algum tratamento que seria espalhado em todos os controllers. No nosso exemplo criamos um método anotado com @ExceptionHandler, mas nada nos impede de criar um método anotado com @InitBinder por exemplo. Agora, toda vez que um controller lançar uma exception, caso ninguém forneça um tratamento mais específico, ela vai cair no tratamento global do @ControllerAdvice. Um ponto a mais que merece ser observado é que o envio do email foi feito a partir de outra Thread. Isso é bem importante, já que você não quer fazer seu usuário esperar por conta de um envio de email.

Esse post foi um pouco mais curto e mais simples, entretanto tratar exceptions é uma parte muio importante do sistema. O melhor é que você trate as possibilidades de erro com o mesmo cuidado que você trata as possibilidades de sucesso nas lógicas, para que você forneça feedback positivo o tempo inteiro. Tanto para o usuário quanto para a própria parte interna da aplicação.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s