【问题标题】:How to handle exceptions thrown while rendering a view in Spring MVC?如何处理在 Spring MVC 中渲染视图时引发的异常?
【发布时间】:2012-06-30 01:11:01
【问题描述】:

我有一个 Spring MVC 应用程序,它使用 FreeMarker 作为视图技术(但也许视图技术对我的问题并不重要)。我需要拦截在请求期间可能引发的所有异常。

我已经实现了HandlerExceptionResolver,但是这个解析器只有在控制器内发生异常时才会执行。但是,当控制器返回 ModelAndView 并且在渲染视图时发生异常(因为找不到变量或类似情况),则不会调用异常解析器,而是在浏览器窗口中获得堆栈跟踪。

我还尝试在控制器中使用异常处理程序方法,该方法返回视图并使用 @ExceptionHandler 对其进行注释,但这也不起作用(很可能再次因为异常不是在控制器中而是在视图中引发)。

那么是否有一些 Spring 机制可以让我注册一个捕获视图错误的异常处理程序?

【问题讨论】:

  • 这样的configuration会有帮助吗?
  • @nobeh 不,很遗憾不是。这篇文章简单解释了HandlerExceptionResolver这个东西的用法。这是我已经使用的,但它只捕获在控制器中抛出的异常,而不是在视图中。

标签: java spring spring-mvc freemarker


【解决方案1】:

提前说一句:如果您只需要一个“静态”错误页面而没有太多逻辑和模型准备,那么在您的web.xml 中放置一个<error-page>-Tag 就足够了(请参见下面的示例)。

否则,可能有更好的方法来做到这一点,但这对我们有用:

我们在 web.xml 中使用一个 servlet <filter> 来捕获所有异常并调用我们的自定义 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-Implementation 如下所示:

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

【讨论】:

  • 我没有使用自定义错误处理程序。我要做的就是 - 如果模板处理失败,则将用户重定向到静态错误页面。浏览器显示错误页面内容,但它也显示堆栈跟踪。有什么线索吗?我正在使用弹簧。只是带有 freemarker 的普通 servlet 用于视图。
  • 您好,谢谢您,我在初始化时收到了org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'defaultErrorHandler' is defined。我需要在 servlet.xml 中定义它吗?
  • 有没有办法转发:request.getRequestDispatcher("/WEB-INF/jsp/error/dispatch-error.jsp").forward(request, response); 使用现有的 ViewResolver?
  • 您好 oxc,感谢您的回答。我在我的情况下尝试了您的解决方案,但出现了异常:java.lang.IllegalStateException:在提交响应后无法转发。有什么办法可以解决吗?
  • @Zhil,Stackoverflow 上还有其他问题可能会为您解答这个问题,或许stackoverflow.com/a/2125045/1479482
【解决方案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 Not Found 等)

    我在所有控制器类上扩展了这个类,以确保将错误重定向到 AbstractController。这样我就不需要在特定控制器上使用 ExceptionHandler ,但我可以将全局添加到我的所有控制器中。 (通过扩展 AbstractController 类)。

    编辑: 再次提出您的问题后,我注意到您的观点出现了错误。不确定这种方式是否会捕获该错误..

    希望这会有所帮助!

    【讨论】:

    • 已经试过了。我直接在控制器中添加了这样的方法,而不是在它的某些基类中,但这并没有什么区别。当在控制器方法中抛出异常时调用此异常处理程序,但在视图中抛出异常时不调用。
    • 或者如果错误发生在 servlet 过滤器中。我认为接受的答案是唯一可以在任何地方处理任何错误的答案。
    • 这行不通。模板处理发生在控制器和控制器通知被调用之后。所以我能想到的唯一方法是按照接受的答案的建议添加手线过滤器。不漂亮但有道理。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-16
    • 1970-01-01
    • 2015-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多