返回

细分Spring MVC统一机制:@ExceptionHandler与错误页面使用情况

后端

从坚如磐石的观点出发,没有哪个程序能始终顺风顺水,它们注定会遭遇坎坷。为了这些不可预期的坎坷,Spring MVC提供了全面且灵活的统一异处理机制。它使Spring MVC项目可以捕获、处理乃至记录所有框架层面抛出的异(正常情况下不该由开发者逐个捕捉)。如果开发人员想要对各种请求都进行全局层面的把控,那么他们就需要对这种机制展开更为深入的了解。这份文章将对Spring 5.x的统一异处理机制展开细致讲解,这其中既有理论阐释,也有实例实践。

什么是统一异处理机制?

Spring MVC的统一异处理机制是一种将错误处理逻辑抽离到统一位置的处理方式,通常情况下它会安置在Spring容器中的某个中心位置。通常来说,这种机制会以三种形式存在:

  1. @ExceptionHandler:可置于方法级和类级,可以对特定Exception及其超类Exception(父类Exception)进行捕获处理。

  2. 错误页面:映射不同的HTTP状态码,并呈现不同的错误页面。

  3. 容器ExceptionHandler:截获一切未由@ExceptionHandler或错误页面捕获的Exception,同时渲染特定错误页面。

@ExceptionHandler的运作机制

捕获Exception最直接了了的手法莫过于使用@ExceptionHandler,这种方式让Exception的处理逻辑不会与常规业务处理混为一谈。它支持方法级和类级两个维度,且会依据Exception的类型及其超类Exception进行捕获。

在此,方法级用以捕获继承而来的所有Exception,换言之,就是指继承而来的exception及其超类exception。与方法级不同,类级会全局监听Exception,换言之,就是指所有抛出的Exception和它们的超类Exception。

这种处理方式的优越性显而易见:

  • 可以单独对每个Exception或其超类Exception定制单独的处理方式,这样就消除了重复处理的顾虑。
  • 只需很小改动,你就可以轻松修改错误处理逻辑,大幅降低维护负担。
  • 在方法级和类级维度的抉择,为处理逻辑的可定制化带来了不限可能,随取随用。
  • @ExceptionHandler对各种Web请求有效,它们适用于POST、GET等不同的请求。
  • @ExceptionHandler可以捕获由过滤器或拦截器中抛出的Exception,这也表明了它对Spring WebFilter的强力支持。

在开始将这种手段作为你武器库的一部分之前,需要切记Exception不可能被嵌套捕获。这意味着,先由父类Exception捕捉的Exception无法再被自己的子Exception捕捉,这一切都需要遵循一个先后的逻辑次序。

使用@ExceptionHandler是一个两步走的过程:

  1. 在相关Controller中定义你要使用的@ExceptionHandler,以下示例以某个Controller命名为ProductController:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class ProductController {

  @ExceptionHandler(Exception.class)
  public ModelAndView generalError(Exception e) {
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("error", e.getMessage());
    modelAndView.addObject("status", 500);
    modelAndView.addObject("title", "General Error");
    modelAndView.addObject("message", "请联系网站管理者。");
    modelAndView.addObject("page", "/error/general");
    return modelAndView;
  }
}

在@ControllerAdvice中添加相应注释;

  1. 指定Exception类型,并执行相应的处理逻辑。
@ExceptionHandler(ProductNotFoundException.class)
public ModelAndView handleProductNotFound(ProductNotFoundException e) {
  ModelAndView modelAndView = new ModelAndView();
  modelAndView.addObject("error", e.getMessage());
  modelAndView.addObject("status", 404);
  modelAndView.addObject("title", "Product Not Found");
  modelAndView.addObject("message", "抱歉,我们无法找到这个商品。");
  modelAndView.addObject("page", "/error/productNotFound");
  return modelAndView;
}

你会发现,当给定某些请求抛出Exception,例如:

// 产品ID不符合要求
@GetMapping("/product/{id}")
public String getProduct(int id) {
  if (id < 0) {
    throw new ProductNotFoundException("产品ID无法是负数");
  }
  // 产品不存在
  if (id > 100) {
    throw new ProductNotFoundException("抱歉,我们暂时没有该产品。");
  }
  return "product";
}

你可以直接观察到,整个错误处理过程都和业务逻辑分离。正因为如此,代码读起来更为容易,维护起来也简单不少。

错误页面使用手册

Spring MVC提供了以错误页面作为形式的第二种统一异处理机制。这种方式让开发人员能够随心所欲地掌控对不同HTTP状态码的把控,比如捕获404错误码(没有找到页面)和500错误码(服务器出错)的Exception。

错误页面工作机制与@ExceptionHandler类似。二者最大区别是,错误页面会根据错误码的类型,以一种定制化、有针对性的方式来定制页面,进而将其传回客户端。

错误页面可用于为不同错误码定义的URL,并利用它们为相应的错误码生成HTML页面。需要注意的是,所有这些错误页面都必须在Web应用程序中保存。

实现这一过程,最简单的莫过于:

  1. 在Web应用程序中,新建一个以“error”为命名的文件夹。

  2. 在“error”文件夹中,添加你想要定义的错误页面。比如下面这个例子:

<!DOCTYPE html>
<html>
<head>
  
</head>
<body>
  <h1>Not Found</h1>
  <p>抱歉,我们无法找到这个页面。</p>
</body>
</html>

这就是“error/404.html”页面。

  1. 在springmvc-servlet.xml添加如下配置。
<bean id="simpleViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/pages/" />
  <property name="suffix" value=".jsp" />
</bean>

完成以上步骤,你就能够捕获所有错误状态码。通常,会抛出以下Exception:

  1. HttpSessionRequiredException(400错误码,请求需要Session信息,但Session信息并未在请求中找到。)

  2. HttpRequestMethodNotSupportedException(405错误码,请求使用不支持的请求方法。)

  3. NoHandlerFoundException(404错误码,请求的资源在服务器上无法被找到或处理。)

  4. Exception(500错误码,服务器已知晓错误,但无法处理)

容器ExceptionHandler的作用机制

容器ExceptionHandler是一种全局型的ExceptionHandler,可以对一切未被容器抛出或未被错误页面捕获的Exception进行截获,默认情况下,这类Exception都会被定位“意外Exception”。

指定容器ExceptionHandler时,遵循如下步骤:

  1. 在Web应用程序中,添加exception.properties文件,并设置error.page参数:
error.page=404.html
  1. 在springmvc-servlet.xml中,增加如下配置:
<bean id="propertyPlaceholderConfig"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfig">
  <property name="propertiesRef" value="classpath:exception.properties" />
</bean>

容器ExceptionHandler主要有3个步骤:

  1. 容器ExceptionHandler会捕获Exception,进而对相应的错误码(例如404或500)进行映射,从而定位相应的错误页面。

  2. 容器ExceptionHandler以类似@ExceptionHandler和错误页面那样,将ErrorPages(错误页面)作为参数,进而将Exception与错误页面进行绑定。

  3. 容器ExceptionHandler会根据HTTP响应的MIME类型(即“text/html”, 或“application/json”),选择错误页面。