1、SpringMVC全局异常处理的四种方式
在项目上线之后,往往会出现一些不可预料的异常信息,对于逻辑性或设计性问题,开发人员或者维护人员需要通过日志,查看异常信息并排除异常;而对于用户,则需要为其呈现出其可以理解的异常提示页面,让用户有一个良好的使用体验。所以异常的处理对于一个Web项目来说是非常重要的。Spring MVC提供了强大的异常处理机制。
SpringMVC提供的异常处理主要有以下四种方式:
- 使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver
- 实现异常处理接口 HandlerExceptionResolver
- 使用 @ExceptionHandler 注解实现异常处理
- 使用 @ControllerAdvice + @ExceptionHandler注解
注意:如果XML中也配置了相同的映射关系,那么SpringMVC会优先采纳基于注解的映射
在SpringMVC中处理异常的推荐方式:使用 @ControllerAdvice + @ExceptionHandler 注解实现全局异常处理。
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属性:设置将异常对象存入请求域时使用的属性名 -->
<!-- 如果没有配置这个属性,那么默认使用"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>
测试的结果如下图所示:
3、通过实现HandlerExceptionResolver接口
上面使用的是Spring MVC定义好的SimpleMappingExceptionResolver异常处理器,可以实现发生指定异常后跳转到指定的页面。但若要实现在捕获到指定异常时,执行一些额外操作它是完成不了的。此时,就需要自定义异常处理器,需要使用到HandlerExceptionResolver接口。
首先新建一个自定义异常类 CustomException:
/**
* 自定义异常
*/
public class CustomException extends Exception{
private String message;
public CustomException(String message) {
super(message);
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
然后创建一个实现 HandlerExceptionResolver 接口的实现类,并且实现其唯一的方法resolveException(),这种方式可以进行全局的异常处理。
/**
* 异常处理,通过实现HandlerExceptionResolver接口来实现
*/
@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,也就是说,可以通过这个返回值类设置发出异常时显示的页面。
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("我抛出了一个异常!!!");
}
//处理自定义异常
@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 注解配合使用。
5、用 @ControllerAdvice+@ ExceptionHandler注解(推荐)
上面说到 @ExceptionHandler注解标注的异常处理方法必须与出错的方法在同一个Controller里面,所以这种方式是只对应单个Controller类。那么此时有一种更好的解决方案:可以使用@ControllerAdvice+@ExceptionHandler注解来解决,这个是 Spring 3.2 带来的新特性。
两者一起使用的作用域:全局异常处理,针对全部Controller中的指定异常类
@ControllerAdvice和@ ExceptionHandler 这两个注解配合使用的代码如下:
/**
* 基于注解的异常处理 @ControllerAdvice+@ExceptionHandler
*/
// 这个注解表示当前类是一个异常映射类
@ControllerAdvice
public class MyException {
// 在@ExceptionHandler注解中指定异常类型
@ExceptionHandler(value = {CustomException.class, ArithmeticException.class})
public ModelAndView exceptionMapping(Exception exception) {// 方法形参位置接收SpringMVC捕获到的异常对象
// 可以将异常对象存入模型;将展示异常信息的视图设置为逻辑视图名称
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("exception", exception);
modelAndView.setViewName("show-annotation-message");
// 打印一下信息
System.out.println(exception.getMessage());
return modelAndView;
}
}
注:@ControllerAdvice 注解的内部是使用@Component 注解修饰的,可以点进源码查看运行:
@Component,@Service,@Controller,@Repository注解修饰的类,就是把这个类的对象交由Spring IOC容器来管理,相当于配置文件中的 <bean id="" class=""/>
。