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=""/>
。