1 异常处理
Spring 提供了多种方式将异常转换为响应:
- 特定的 Spring 异常将会自动映射为指定的 HTTP 状态码;
- 异常上可以添加
@ResponseStatus注解,从而将其映射为某一个 HTTP 状态码; - 在方法上可以添加
@ExceptionHandler注解,使其用来处理异常。
1.1 将异常映射为 HTTP 状态码
| Spring 异常 | HTTP 状态码 |
|---|---|
| BindException | 400 - Bad Request |
| ConversionNotSupportedException | 500 - Internal Server Error |
| HttpMediaTypeNotAcceptableException | 406 - Not Acceptable |
| HttpMediaTypeNotSupportedException | 415 - Unsupported Media Type |
| HttpMessageNotReadableException | 400 - Bad Request |
| HttpMessageNotWritableException | 500 - Internal Server Error |
| HttpRequestMethodNotSupportedException | 405 - Method Not Allowed |
| MethodArgumentNotValidException | 400 - Bad Request |
| MissingServletRequestParameterException | 400 - Bad Request |
| MissingServletRequestPartException | 400 - Bad Request |
| NoSuchRequestHandlingMethodException | 404 - Not Found |
| TypeMismatchException | 400 - Bad Request |
表中的异常一般会由 Spring 自身抛出,作为 DispatcherServlet 处理过程中或执行校验时出现问题的结果。例如,如果 DispatcherServlet 无法找到适合处理请求的控制器方法,那么将会抛出 NoSuchRequestHandlingMethod-Exception 异常,最终的结果就是产生 404 状态码的响应(Not Found)。
除了上述的默认映射外,Spring 提供了一种机制,能够通过 @ResponseStatus 注解将异常映射为 HTTP 状态码。
假如当从数据库寻找某对象失败时抛出 SpittleNotFoundException 异常,若不进行处理,一般情况下会返回 500 状态码。此处,对状态码进行了变更,通过 @ResponseStatus 注解将异常映射为 404 Not Found。若控制器抛出该异常,那么将返回该状态码。
package spittr.web;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Spittle Not Found")
public class SpittleNotFoundException extends RuntimeException {
}2 全局异常处理
2.1 SpringMVC 全局异常处理的四种方式
在项目上线之后,往往会出现一些不可预料的异常信息,对于逻辑性或设计性问题,开发人员或者维护人员需要通过日志,查看异常信息并排除异常;而对于用户,则需要为其呈现出其可以理解的异常提示页面,让用户有一个良好的使用体验。所以异常的处理对于一个 Web 项目来说是非常重要的。Spring MVC 提供了强大的异常处理机制。
Spring MVC 提供的异常处理主要有以下四种方式:
- 使用 Spring MVC 提供的简单异常处理器
SimpleMappingExceptionResolver - 实现异常处理接口
HandlerExceptionResolver - 使用
@ExceptionHandler注解实现异常处理 - 使用
@ControllerAdvice+@ExceptionHandler注解 (推荐)
注意:如果 XML 中也配置了相同的映射关系,那么 SpringMVC 会优先采纳基于注解的映射
2.2 通过 SimpleMappingExceptionResolver 实现
SimpleMappingExceptionResolver 异常处理器是 SpringMVC 定义好的异常处理器。使用 SimpleMappingExceptionResolver 进行异常处理的优缺点:
- 优点:集成简单、有良好的扩展性、对已有代码没有入侵性等
- 缺点:该方法仅能获取到异常信息,若在出现异常时,对需要获取除异常以外的数据的情况不适用。
下面在 SpringMVC 的 XML 文件中配置 SimpleMappingExceptionResolver 对象,配置如下。
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" id="exceptionResolver">
<!-- 指定默认的异常响应页面。若发生的异常不是exceptionMappings中指定的异常,则使用默认异常响应页面。 -->
<property name="defaultErrorView" value="error"></property>
<!-- exceptionAttribute属性:设置将异常对象存入 request 域时使用的属性名 -->
<!-- 如果没有配置这个属性,那么默认使用"exception"作为属性名。源码中文档说明如下:
* Set the name of the model attribute as which the exception should be exposed. Default is "exception". -->
<property name="exceptionAttribute" value="exception"></property>
<!-- 用于指定具体的不同类型的异常所对应的异常响应页面。 -->
<property name="exceptionMappings">
<props>
<!-- key属性:指定异常类型 -->
<!-- 文本标签体:指定和异常对应的逻辑视图名称 -->
<prop key="java.lang.ArithmeticException">show-message</prop>
<prop key="java.lang.RuntimeException">show-runtime-message</prop>
<prop key="java.lang.Exception">show-exception-message</prop>
</props>
</property>
</bean>上面配置了三个异常类型,那么它的匹配规则是什么呢?是从最大的异常开始,还是精确匹配呢?
- 匹配规则 1:如果异常对象能够在映射关系中找到精确匹配的规则,那么就执行这个精确匹配的规则
- 匹配规则 2:如果异常对象能够在映射关系中找到多个匹配的规则,优先采纳精确匹配的规则
- 匹配规则 3:如果异常对象能够在映射关系中找到多个匹配的规则,且没有精确匹配的,那么会采纳范围更接近的那个
- 匹配规则 4:如果注解中也配置了相同的映射关系,那么 SpringMVC 会优先采纳基于注解的映射
结论:在整个 SpringMVC 全局异常处理中,当异常发生时,会优先采取精确匹配的规则,没有的话会采纳范围更接近的那个,其它的同理。
下面创建模拟出现异常的 Controller 方法:
@RequestMapping(value = "/exception")
public String exceptionHandler(){
// 模拟出现异常
System.out.println(10 / 0);
return "success";
}用于展示异常信息的页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>异常信息页面</title>
</head>
<body>
<h1>系统信息</h1>
异常对象:${requestScope.exception}<br />
异常消息:${requestScope.exception.message}<br />
</body>
</html>测试的结果如下图所示:

2.3 通过实现 HandlerExceptionResolver 接口
上面使用的是 Spring MVC 定义好的 SimpleMappingExceptionResolver 异常处理器,可以实现发生指定异常后跳转到指定的页面。但若要实现在捕获到指定异常时,执行一些额外操作它是完成不了的。此时,就需要自定义异常处理器,需要使用到 HandlerExceptionResolver 接口。
首先新建一个自定义异常类 CustomException:
@Data
public class CustomException extends Exception{
private String message;
public CustomException(String message) {
super(message);
this.message = message;
}
}然后创建一个实现 HandlerExceptionResolver 接口的实现类,并且实现其唯一的方法 resolveException (),这种方式可以进行全局的异常处理。
@Component
public class GlobalException implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception exception) {
System.out.println(exception.getMessage());
CustomException customException = null;
if (exception instanceof CustomException){
customException = (CustomException) exception;
}else {
customException = new CustomException("出现了未知的错误!!!");
}
String message = customException.getMessage();
System.out.println(message);
System.out.println("---------");
ModelAndView mv = new ModelAndView();
mv.addObject("exception",customException);
mv.setViewName("show-exception-message");
return mv;
}
}ResolveException 方法的参数“Exception e”即为 Controller 或其下层抛出的异常。参数“Object o”就是处理器适配器要执行的 Handler 对象。ResolveException 方法的返回值类型是 ModelAndView,也就是说,可以通过这个返回值类设置发出异常时显示的页面。
2.4 使用 @ExceptionHandler 注解
@ExceptionHandler 注解用来将一个方法标注为异常处理方法。 该注解中只有一个可选的属性 value,是一个 Class<?>数组,用于指定该注解的方法所要处理的异常类,即所要匹配的异常。被该注解修饰的方法的返回值为异常处理后的跳转页面,其返回值可以是 ModelAndView、String,或 void;方法名随意,方法的参数可以是 Exception 及其子类对象、Model、HttpServletRequest、HttpServletResponse 等。系统会自动为这些方法参数赋值。
@ExceptionHandler 注解处理异常的作用域:单个类,只针对当前 Controller。
@Controller
public class ExceptionController {
@RequestMapping(value = "/exception1")
public String exception1() {
System.out.println(10 / 0);
return "success";
}
@RequestMapping(value = "/exception2")
public void exception2() throws CustomException {
throw new CustomException("我抛出了一个异常!!!");
}
//处理自定义异常
// Exception e 参数必须,Model 不必须
@ExceptionHandler({CustomException.class, ArithmeticException.class})
public String exceptionHandler1(Exception e, Model model) {
// 打印错误信息
System.out.println(e.getMessage());
e.printStackTrace();
// 将错误数据存入请求域
model.addAttribute("exception", e);
return "show-annotation-message";
}
}注意:如果在 Controller 中单独使用这个注解是有缺陷的,就是不能够全局处理异常,因为进行异常处理的方法必须与出错的方法在同一个 Controller 里面,也就是说每个 Controller 类中都要写一遍,所以实用性不高。
解决方案:可以将处理异常的信息抽取出来放在一个 BaseController,然后对需要处理异常的 Controller 继承该类即可。
public class BaseController {
@ExceptionHandler({CustomException.class, ArithmeticException.class})
public String exceptionHandler1(Exception e, Model model) {
System.out.println(e.getMessage());
e.printStackTrace();
model.addAttribute("exception", e);
return "show-annotation-message";
}
}但是还是存在同样的问题,每个类都得继承它,可见这种方式同样不可取,所以一般使用下面这种方式:@ControllerAdvice 和@ ExceptionHandle 注解配合使用。
2.5 用 @ControllerAdvice+@ ExceptionHandler 注解(推荐)
上面说到 @ExceptionHandler 注解标注的异常处理方法必须与出错的方法在同一个 Controller 里面,所以这种方式是只对应单个 Controller 类。那么此时有一种更好的解决方案:可以使用@ControllerAdvice+@ExceptionHandler 注解来解决,这个是 Spring 3.2 带来的新特性。一般情况下,可以设定 Exception 接口,然后根据不同的对象 or 服务去实现。在实现类上添加 @ControllerAdvice,实现方法上添加 @ExceptionHandler。
两者一起使用的作用域:全局异常处理,针对全部 Controller 中的指定异常类
@ControllerAdvice 和@ ExceptionHandler 这两个注解配合使用的代码如下:
// 异常处理类
@ControllerAdvice
public class MyException {
// 在@ExceptionHandler 注解中指定异常类型
@ExceptionHandler(value = {CustomException.class, ArithmeticException.class})
public ModelAndView exceptionMapping(Exception exception) {
// 可以将异常对象存入模型;将展示异常信息的视图设置为逻辑视图名称
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("exception", exception);
modelAndView.setViewName("show-annotation-message");
return modelAndView;
}
//处理其它异常
@ExceptionHandler
public ModelAndView exceptionMapping(Exception exception) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("exception", exception);
modelAndView.setViewName("defaultException");
return modelAndView;
}
}注:@ControllerAdvice 注解的内部是使用@Component 注解修饰的,可以点进源码查看运行:

@Component,@Service,@Controller,@Repository 注解修饰的类,就是把这个类的对象交由 Spring IOC 容器来管理,相当于配置文件中的 <bean id="" class=""/>。
SpringMVC 的配置文件
1)组件扫描器,扫描@Controller 注解
2)组件扫描器,扫描@ControllerAdvice 所在的包名
3)声明注解驱动
<!--处理需要的两步-->
<context:component-scan base-package="com.bjpowernode.handler" />
<mvc:annotation-driven />