web-dev-qa-db-fra.com

Comment gérer les exceptions levées lors du rendu d'une vue dans Spring MVC?

J'ai une application Spring MVC qui utilise FreeMarker comme technologie View (Mais peut-être que la technologie view n'a pas vraiment d'importance pour ma question). J'ai besoin d'intercepter toutes les exceptions qui peuvent être levées lors d'une demande.

J'ai implémenté un HandlerExceptionResolver mais ce résolveur n'est exécuté que lorsque l'exception se produit dans un contrôleur. Mais lorsqu'un contrôleur renvoie un ModelAndView et que l'exception se produit lors du rendu de la vue (car une variable n'a pas été trouvée ou quelque chose comme ça), le résolveur d'exception n'est pas appelé et à la place, j'obtiens une trace de pile dans la fenêtre du navigateur.

J'ai également essayé d'utiliser une méthode de gestionnaire d'exceptions dans le contrôleur qui renvoie la vue et l'a annotée avec @ExceptionHandler mais cela ne fonctionne pas non plus (probablement encore parce que l'exception n'est pas lancée dans le contrôleur mais dans la vue).

Y a-t-il donc un mécanisme Spring où je peux enregistrer un gestionnaire d'exceptions qui capture les erreurs d'affichage?

27
kayahr

Un mot d'avance: si vous avez juste besoin d'une page d'erreur "statique" sans beaucoup de logique et de préparation de modèle, il devrait suffire de mettre un <error-page>- Ajoutez un tag dans votre web.xml (voir ci-dessous pour un exemple).

Sinon, il pourrait y avoir de meilleures façons de le faire, mais cela fonctionne pour nous:

Nous utilisons un servlet <filter> dans le web.xml qui intercepte toutes les exceptions et appelle notre ErrorHandler personnalisé, le même que nous utilisons dans le Spring HandlerExceptionResolver.

<filter>
   <filter-name>errorHandlerFilter</filter-name>
   <filter-class>org.example.filter.ErrorHandlerFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>errorHandlerFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

L'implémentation ressemble essentiellement à ceci:

public class ErrorHandlerFilter implements Filter {

  ErrorHandler errorHandler;

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
    try {
      filterChain.doFilter(request, response);
    } catch (Exception ex) {
      // call ErrorHandler and dispatch to error jsp
      String errorMessage = errorHandler.handle(request, response, ex);
      request.setAttribute("errorMessage", errorMessage);
      request.getRequestDispatcher("/WEB-INF/jsp/error/dispatch-error.jsp").forward(request, response);
    }

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    errorHandler = (ErrorHandler) WebApplicationContextUtils
      .getRequiredWebApplicationContext(filterConfig.getServletContext())
      .getBean("defaultErrorHandler");
  }

  // ...
}

Je pense que cela devrait fonctionner à peu près de la même manière pour les modèles FreeMarker. Bien sûr, si votre vue d'erreur génère une erreur, vous êtes plus ou moins à court d'options.

Pour détecter également des erreurs telles que 404 et préparer le modèle, nous utilisons un filtre qui est mappé au répartiteur ERROR:

<filter>
   <filter-name>errorDispatcherFilter</filter-name>
   <filter-class>org.example.filter.ErrorDispatcherFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>errorDispatcherFilter</filter-name>
  <url-pattern>/*</url-pattern>
  <dispatcher>ERROR</dispatcher>
</filter-mapping>

<error-page>
  <error-code>404</error-code>
  <location>/WEB-INF/jsp/error/dispatch-error.jsp</location>
</error-page>
<error-page>
  <exception-type>Java.lang.Exception</exception-type>
  <location>/WEB-INF/jsp/error/dispatch-error.jsp</location>
</error-page>

L'implémentation doFilter ressemble à ceci:

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

  final HttpServletRequest request = (HttpServletRequest) servletRequest;

  // handle code(s)
  final int code = (Integer) request.getAttribute("javax.servlet.error.status_code");
  if (code == 404) {
    final String uri = (String) request.getAttribute("javax.servlet.error.request_uri");
    request.setAttribute("errorMessage", "The requested page '" + uri + "' could not be found.");
  }

  // notify chain
  filterChain.doFilter(servletRequest, servletResponse);
}
21
oxc

Vous pouvez étendre le DispatcherServlet.

Dans votre web.xml, remplacez le DispatcherServlet générique pour votre propre classe.

<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>com.controller.generic.DispatcherServletHandler</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

Créez plus tard votre propre classe DispatcherServletHandler et étend à partir de DispatcherServlet:

public class DispatcherServletHandler extends DispatcherServlet {

    private static final String ERROR = "error";
    private static final String VIEW_ERROR_PAGE = "/WEB-INF/views/error/view-error.jsp";

    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try{
            super.doService(request, response);
        } catch(Exception ex) {
            request.setAttribute(ERROR, ex);
            request.getRequestDispatcher(VIEW_ERROR_PAGE).forward(request, response);
        }
     }
}

Et dans cette page, nous n'avons qu'à montrer un message à l'utilisateur.

7
cralfaro

Je ne sais pas si ma solution fonctionne avec le problème que vous rencontrez. Je posterai juste la façon dont j'attrape mes exceptions pour m'assurer qu'aucune trace de pile ne s'affiche dans le navigateur:

J'ai créé une classe AbstractController avec une méthode qui gérera un conflit spécifique comme celui-ci:

public class AbstractController {

    @ResponseStatus(HttpStatus.CONFLICT)
    @ExceptionHandler({OptimisticLockingFailureException.class})
    @ResponseBody
    public void handleConflict() {
    //Do something extra if you want
    }
}

De cette façon, chaque fois qu'une exception se produit, l'utilisateur verra un état HTTPResponse par défaut. (ex. 404 Introuvable etc.)

J'étends cette classe sur toutes mes classes de contrôleur pour m'assurer que les erreurs sont redirigées vers AbstractController. De cette façon, je n'ai pas besoin d'utiliser ExceptionHandler sur un contrôleur spécifique, mais je peux ajouter le global à tous mes contrôleurs. (en étendant la classe AbstractController).

Edit: Après un autre examen de votre question, j'ai remarqué que vous obtenez des erreurs dans votre vue. Je ne sais pas si cette façon va attraper cette erreur ..

J'espère que cela t'aides!!

0
Byron Voorbach