Подтвердить что ты не робот

Как обрабатывать исключения, возникающие при рендеринге представления в Spring MVC?

У меня есть приложение Spring MVC, которое использует технологию FreeMarker as View (но, возможно, технология просмотра не имеет большого значения для моего вопроса). Мне нужно перехватить все исключения, которые могут быть брошены во время запроса.

Я реализовал HandlerExceptionResolver, но этот резольвер выполняется только тогда, когда исключение возникает в контроллере. Но когда контроллер возвращает ModelAndView, и исключение возникает при рендеринге представления (поскольку переменная не была найдена или что-то вроде этого), тогда резольвер исключений не вызывается, и вместо этого я получаю трассировку стека в окне браузера.

Я также попытался использовать метод обработчика исключений в контроллере, который возвращает представление и аннотирует его с помощью @ExceptionHandler, но это также не работает (скорее всего, опять же, потому что исключение не выбрано в контроллере, а в представлении).

Итак, есть ли какой-то механизм Spring, где я могу зарегистрировать обработчик исключений, который фиксирует ошибки просмотра?

4b9b3361

Ответ 1

Слово заранее: если вам просто нужна "статическая" страница с ошибкой без особой логики и подготовки модели, достаточно поместить <error-page> -Tag в ваш web.xml (см. Пример ниже).

В противном случае, могут быть лучшие способы сделать это, но это работает для нас:

Мы используем сервлет <filter> в web.xml который перехватывает все исключения и вызывает наш пользовательский ErrorHandler, то же самое, что мы используем внутри 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>

Реализация выглядит по существу так:

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

  // ...
}

Я считаю, что это должно работать примерно так же для шаблонов FreeMarker. Конечно, если ваше представление об ошибке выдает ошибку, у вас более или менее нет выбора.

Чтобы также отловить ошибки типа 404 и подготовить модель для нее, мы используем фильтр, который сопоставлен с диспетчером 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>

Реализация doFilter выглядит следующим образом:

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

Ответ 2

Вы можете расширить DispatcherServlet.

В вашем web.xml замените общий DispatcherServlet для вашего собственного класса.

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

Позже создайте свой собственный класс DispatcherServletHandler и продолжите его из 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);
        }
     }
}

И на этой странице нам нужно только показать сообщение пользователю.

Ответ 3

Не уверен, что мое решение работает с проблемой, с которой вы сталкиваетесь. Я просто отправлю так, как я поймаю свои исключения, чтобы убедиться, что в браузере не отображается трассировка стека:

Я создал класс AbstractController с помощью метода, который будет обрабатывать определенный конфликт следующим образом:

public class AbstractController {

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

Таким образом, всякий раз, когда возникает исключение, пользователь увидит статус HTTPResponse по умолчанию. (например, 404 не найдено и т.д.)

Я расширяю этот класс на всех моих классах контроллеров, чтобы убедиться, что ошибки перенаправлены на AbstractController. Таким образом, мне не нужно использовать ExceptionHandler на определенном контроллере, но я могу добавить глобально ко всем моим контроллерам. (путем расширения класса AbstractController).

Edit: После другого вопроса, я заметил, что вы получаете ошибки в своем представлении. Не уверен, что этот путь поймает эту ошибку.

Надеюсь, это поможет!