1. HandlerExceptionResolver
Spring HandlerExceptionResolver实现处理控制器执行期间发生的异常。 HandlerExceptionResolver有点类似于可以在Web应用程序描述web.xml中定义的异常映射。但是,它们提供了更灵活的方法。例如,它们提供有关在抛出异常时正在执行哪个处理器的信息。此外,处理异常的编程方式提供了更多选项,以便在请求转发到另一个URL之前进行适当的响应(与使用Servlet特定异常映射时的最终结果相同)。
除了实现HandlerExceptionResolver接口,这只是实现 resolveException(Exception, Handler) 方法并返回ModelAndView的问题,还可以使用提供的SimpleMappingExceptionResolver或创建 @ExceptionHandler 方法。 SimpleMappingExceptionResolver可以获取可能抛出的任何异常的类名,并将其映射到视图名称。这在功能上等同于Servlet API中的异常映射功能,但也可以实现来自不同处理器的更精细的异常映射。另一方面,@ExceptionHandler注解还可用于处理异常的方法上。这些方法可以在@Controller中定义,也可以在@ControllerAdvice类中定义时以用来应用于更多@Controller类。
2. @ExceptionHandler
HandlerExceptionResolver接口和SimpleMappingExceptionResolver实现允许在转发到这些视图之前,以声明方式将异常映射到特定视图以及一些可选的Java逻辑。但是,在某些情况下,特别是在依赖@ResponseBody方法而不是视图解析器时,直接设置响应的状态并可选地将错误内容写入响应体可能更方便。
可以使用@ExceptionHandler方法执行此操作。在控制器中声明时,此类方法适用于该控制器(或其任何子类)的@RequestMapping方法引发的异常。还可以在@ControllerAdvice类中声明@ExceptionHandler方法,在这种情况下,它会处理来自许多控制器的@RequestMapping方法的异常。下面是一个控制器本地@ExceptionHandler方法的示例:
@Controller
public class SimpleController {
// ...
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
该异常可能与传播的顶级异常(即抛出直接IOException)或顶级包装器异常中的直接原因(例如,包含在IllegalStateException内的IOException)相匹配。
对于匹配的异常类型,最好将目标异常声明为方法参数,如上所示。 当多个异常方法匹配时,根异常匹配通常优先于原因异常匹配。 更具体地说,ExceptionDepthComparator用于根据抛出的异常类型的深度对异常进行排序。
或者,可以将@ExceptionHandler值设置为异常类型数组。 如果抛出与列表中的某个类型的异常相匹配,则将调用使用匹配的@ExceptionHandler注释的方法。 如果未设置注解值,则将使用声明的方法参数类型进行匹配。
注:对于@ExceptionHandler方法,在特定控制器或建言器bean的处理器方法中,根异常匹配将优先于匹配当前异常的原因。但是,较高优先级的@ControllerAdvice上的原因匹配仍然优先于较低优先级的建言器bean上的任何匹配(无论是根异常还是原因级别)。因此,在使用多建议安排时,请在具有相应顺序的优先级建言器bean上声明主根异常映射。
与使用@RequestMapping注解注释的标准控制器方法非常相似,@ExceptionHandler方法的方法参数和返回值可以是灵活的。例如,可以在Servlet环境中访问HttpServletRequest,在Portlet环境中访问PortletRequest。返回类型可以是String,它被解释为视图名称,ModelAndView对象,ResponseEntity,或者还可以添加@ResponseBody以使用消息转换器转换方法返回值并将其写入响应流。
最后,@ExceptionHandler方法实现可以选择通过以原始形式重新抛出它来退出处理给定的异常实例。这在只对根级别匹配或在无法静态确定的特定上下文中的匹配中感兴趣的情况下非常有用。重新抛出的异常将通过其余的解析链传播,就像给定的@ExceptionHandler方法一开始没有匹配一样。
3. 处理标准的Spring MVC异常
Spring MVC在处理请求时可能会引发许多异常。 SimpleMappingExceptionResolver可以根据需要轻松地将任何异常映射到默认错误视图。但是,在与以自动方式解释响应的客户端一起工作时,需要在响应上设置特定的状态代码。根据引发的异常,状态代码可能指示客户端错误(4xx)或服务器错误(5xx)。
DefaultHandlerExceptionResolver将Spring MVC异常转换为特定的错误状态代码。它默认注册了MVC命名空间,MVC Java配置,以及DispatcherServlet(即不使用MVC命名空间或Java配置时)。下面列出了此解析程序处理的一些例外情况以及相应的状态代码:
Exception | HTTP Status Code |
---|---|
|
400 (Bad Request) |
|
500 (Internal Server Error) |
|
406 (Not Acceptable) |
|
415 (Unsupported Media Type) |
|
400 (Bad Request) |
|
500 (Internal Server Error) |
|
405 (Method Not Allowed) |
|
400 (Bad Request) |
|
500 (Internal Server Error) |
|
400 (Bad Request) |
|
400 (Bad Request) |
|
404 (Not Found) |
|
404 (Not Found) |
|
400 (Bad Request) |
DefaultHandlerExceptionResolver通过设置响应的状态透明地工作。但是,如果应用程序需要在每个错误响应中添加开发人员友好的内容,例如在提供REST API时,它就不会将任何错误内容写入响应主体。可以通过视图解析器准备ModelAndView并呈现错误内容 --- 即通过配置ContentNegotiatingViewResolver,MappingJackson2JsonView等。但是,可能更喜欢使用@ExceptionHandler方法。
如果更喜欢通过@ExceptionHandler方法编写错误内容,则可以扩展ResponseEntityExceptionHandler。这是@ControllerAdvice类的一个方便的基础,它提供了一个@ExceptionHandler方法来处理标准的Spring MVC异常并返回ResponseEntity。这允许使用消息转换器自定义响应并写入错误内容。
4. 使用@ResponseStatus注释业务异常
可以使用@ResponseStatus注释业务异常。引发异常时,ResponseStatusExceptionResolver通过相应地设置响应的状态来处理它。默认情况下,DispatcherServlet注册ResponseStatusExceptionResolver并且可以使用它。
5. 自定义默认Servlet容器错误页面
当响应的状态设置为错误状态代码并且响应体为空时,Servlet容器通常会呈现HTML格式的错误页面。要自定义容器的默认错误页面,可以在web.xml中声明<error-page>元素。直到Servlet 3,该元素必须映射到特定的状态代码或异常类型。从Servlet 3开始,不需要映射错误页面,这实际上意味着指定的位置自定义默认的Servlet容器错误页面。
<error-page>
<location>/error</location>
</error-page>
请注意,错误页面的实际位置可以是JSP页面或容器中的其他URL,包括通过@Controller方法处理的URL:
在编写错误信息时,可以通过控制器中的请求属性访问HttpServletResponse上设置的状态代码和错误消息:
@Controller
public class ErrorController {
@RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", request.getAttribute("javax.servlet.error.status_code"));
map.put("reason", request.getAttribute("javax.servlet.error.message"));
return map;
}
}
或者在JSP中:
<%@ page contentType="application/json" pageEncoding="UTF-8"%>
{
status:<%=request.getAttribute("javax.servlet.error.status_code") %>,
reason:<%=request.getAttribute("javax.servlet.error.message") %>
}