Spring中的@ControllerAdvice和@ExceptionHandler注解處理全局異常
前言
開發(fā)過程中,難免有的程序會因為某些原因拋出異常,而這些異常一般都是利用try ,catch的方式處理異常或者throw,throws的方式拋出異常不管。
這種方法對于程序員來說處理也比較麻煩,對客戶來說也不太友好,所以我們希望既能方便程序員編寫代碼,不用過多的自己去處理各種異常編寫重復(fù)的代碼又能提升用戶的體驗,這時候全局異常處理就顯得很重要也很便捷了。
一、@ControllerAdvice和@ExceptionHandler簡介
在構(gòu)建RestFul接口的今天,我們一般會限定好返回數(shù)據(jù)的格式,有利于前端調(diào)用解析,比如:
{
"code": 0,
"data": {},
"msg": "操作成功"
}但有時卻往往會產(chǎn)生一些bug,這時候就破壞了返回數(shù)據(jù)的一致性,導(dǎo)致調(diào)用者無法解析。所以我們常常會定義一個全局的異常攔截器。
1.1、@ControllerAdvice
@ControllerAdvice 是Spring 3.2提供的新注解,可以對Controller中使用到@RequestMapping注解的方法做邏輯處理。
@ControllerAdvice ,很多初學者可能都沒有聽說過這個注解,實際上這是一個非常有用的注解。顧名思義,這是一個增強的 Controller,一般配合@ExceptionHandler使用來處理全局異常。注意不能自己try和catch異常,否則就不會被全局異常處理捕獲到。
使用這個注解 ,可以實現(xiàn)三個方面的功能:
- 全局異常處理
- 全局數(shù)據(jù)綁定
- 全局數(shù)據(jù)預(yù)處理
靈活使用這三個功能,可以幫助我們簡化很多工作,需要注意的是,這是 SpringMVC 提供的功能,在 Spring Boot 中可以直接使用,這里只介紹全局異常處理,需要其他功能可以訪問參考鏈接。
1.2、全局異常處理
Springboot對于全局異常的處理做了不錯的支持,它提供了兩個可用的注解。
@ControllerAdvice:用來開啟全局的異常捕獲
@ExceptionHandler:說明捕獲哪些異常,對哪些異常進行處理。
使用 @ControllerAdvice結(jié)合@ExceptionHandler 實現(xiàn)全局異常處理,只需要定義類,添加該注解即可定義方式如下:
//可以使Spring自動把要返回的對象轉(zhuǎn)化成json文本寫入到響應(yīng)體中,比如自定義的ResultBean
@ResponseBody
@ControllerAdvice
public class MyGlobalExceptionHandler
{
// 專門用來捕獲和處理Controller層的異常
@ExceptionHandler(Exception.class)
public ModelAndView customException(Exception e)
{
ModelAndView mv = new ModelAndView();
mv.addObject("message", e.getMessage());
mv.setViewName("myerror");
return mv;
}
// 專門用來捕獲和處理Controller層的空指針異常
@ExceptionHandler(NullPointerException.class)
public ModelAndView nullPointerExceptionHandler(NullPointerException e)
{
ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
mv.addObject("success",false);
mv.addObject("mesg","請求發(fā)生了空指針異常,請稍后再試");
return mv;
}
}在該類中,可以定義多個方法,不同的方法處理不同的異常,例如專門處理空指針的方法、專門處理數(shù)組越界的方法...,也可以直接向上面代碼一樣,在一個方法中處理所有的異常信息。
@ExceptionHandler 注解用來指明異常的處理類型,即如果這里指定為 NullpointerException,則數(shù)組越界異常就不會進到這個方法中來。
二、為什么要做Controller層的異常統(tǒng)一處理以及統(tǒng)一結(jié)果返回
不知道你平時在寫Controller層接口的時候,有沒有注意過拋出異常該怎么處理,是否第一反應(yīng)是想著用個try-catch來捕獲異常?但是這樣地處理只適合那種編譯器主動提示的檢查時異常,因為你不用try-catch就過不了編譯檢查,所以你能主動地抓獲異常并進行處理。但是,如果存在運行時異常且你沒有來得及想到去處理它的時候會發(fā)生什么呢?我們可以來先看看下面的這個沒有處理運行時異常的例子:
@RestController
public class ExceptionRest {
@GetMapping("getNullPointerException")
public Map<String,Object> getNullPointerException(){
throw new NullPointerException("出現(xiàn)了空指針異常");
}
}以上代碼在基于maven的SpringMVC項目中,使用tomcat啟動后,瀏覽器端發(fā)起如下請求:
//localhost:8080/zxtest/getNullPointerException
訪問后得到的結(jié)果是這樣的,瀏覽器收到的報錯信息:

可以看到,我們在Controller接口層拋出了一個空指針異常,然后沒有捕獲,結(jié)果異常堆棧就會返回給前端瀏覽器,給用戶造成了非常不好的體驗。
除此之外,前端從報錯信息中能看到后臺系統(tǒng)使用的服務(wù)器及中間件類型、所采用的框架信息及類信息,甚至如果后端拋出的是SQL異常,那么還可以看到SQL異常的具體查詢的參數(shù)信息,這是一個中危安全漏洞,是必須要修復(fù)的。
三、使用@ExceptionHandler和@ControllerAdvice做到統(tǒng)一處理
當出現(xiàn)這種運行時異常的時候,我們想到的最簡單的方法也許就是給可能會拋出異常的代碼加上異常處理,如下所示:
@RestController
public class ExceptionRest {
private Logger log = LoggerFactory.getLogger(ExceptionRest.class);
@GetMapping("getNullPointerException")
public Map<String,Object> getNullPointerException(){
Map<String,Object> returnMap = new HashMap<String,Object>();
try{
throw new NullPointerException("出現(xiàn)了空指針異常");
}catch(NullPointerException e){
log.error("出現(xiàn)了空指針異常",e);
returnMap.put("success",false);
returnMap.put("mesg","請求發(fā)生異常,請稍后再試");
}
return returnMap;
}
}因為我們手動地在拋出異常的地方加上了處理,并妥善地返回發(fā)生異常時該返回給前端的內(nèi)容,因此,當我們再次在瀏覽器發(fā)起相同的請求時得到就是以下內(nèi)容:
{
success: false,
mesg: "請求發(fā)生異常,請稍后再試"
}貌似問題得到了解決,但是你能確保你可以在所有可能會發(fā)生異常的地方都正好捕獲了異常并處理嗎?你能確保團隊的其他人也這么做?
很明顯,你需要一個統(tǒng)一的異常捕獲與處理方案。
Spring3.2以后,SpringMVC引入了ExceptionHandler的處理方法,使得對異常的處理變得更加簡單和精確,你唯一需要做的就是新建一個Controller,然后再里面加上兩個注解即可完成Controller層所有異常的捕獲與處理。
3.1、@ExceptionHandler和@ControllerAdvice基本使用
新建一個Controller如下:
@ControllerAdvice
public class ExceptionConfigController {
@ExceptionHandler
public ModelAndView exceptionHandler(Exception e){
ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
mv.addObject("success",false);
mv.addObject("mesg","請求發(fā)生了異常,請稍后再試");
return mv;
}
}我們在如上的代碼中,類上加了@ControllerAdvice注解,表示它是一個增強版的controller,然后在里面創(chuàng)建了一個返回ModelAndView對象的exceptionHandler方法,其上加上@ExceptionHandler注解,表示這是一個異常處理方法,然后在方法里面寫上具體的異常處理及返回參數(shù)邏輯即可,如此就完成了所有的工作,真的是太方便了。
我們在瀏覽器發(fā)起調(diào)用后就返回了如下的結(jié)果:
{
success: false,
mesg: "請求發(fā)生了異常,請稍后再試"
}3.2、@ExceptionHandler具體異常的處理
相比與HandlerExceptionResolver而言,使用@ExceptionHandler更能靈活地對不同的異常進行分別的處理。并且,當拋出的異常是指定異常的子類,那么照樣能夠被捕獲和處理。
我們改變下controller層的代碼如下:
@RestController
public class ExceptionController {
@GetMapping("getNullPointerException")
public Map<String, Object> getNullPointerException() {
throw new NullPointerException("出現(xiàn)了空指針異常");
}
@GetMapping("getClassCastException")
public Map<String, Object> getClassCastException() {
throw new ClassCastException("出現(xiàn)了類型轉(zhuǎn)換異常");
}
@GetMapping("getIOException")
public Map<String, Object> getIOException() throws IOException {
throw new IOException("出現(xiàn)了IO異常");
}
}已知NullPointerException和ClassCastException都繼承RuntimeException,而RuntimeException和IOException都繼承Exception。
我們在ExceptionConfigController做這樣的處理:
@ControllerAdvice
public class ExceptionConfigController
{
// 專門用來捕獲和處理Controller層的空指針異常
@ExceptionHandler(NullPointerException.class)
public ModelAndView nullPointerExceptionHandler(NullPointerException e){
ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
mv.addObject("success",false);
mv.addObject("mesg","請求發(fā)生了空指針異常,請稍后再試");
return mv;
}
// 專門用來捕獲和處理Controller層的運行時異常
@ExceptionHandler(RuntimeException.class)
public ModelAndView runtimeExceptionHandler(RuntimeException e){
ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
mv.addObject("success",false);
mv.addObject("mesg","請求發(fā)生了運行時異常,請稍后再試");
return mv;
}
// 專門用來捕獲和處理Controller層的異常
@ExceptionHandler(Exception.class)
public ModelAndView exceptionHandler(Exception e){
ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
mv.addObject("success",false);
mv.addObject("mesg","請求發(fā)生了異常,請稍后再試");
return mv;
}
}當我們在Controller層拋出NullPointerException時,就會被nullPointerExceptionHandler進行處理,然后攔截。
{
success: false,
mesg: "請求發(fā)生了空指針異常,請稍后再試"
}當我們在Controller層拋出ClassCastException時,就會被runtimeExceptionHandler進行處理,然后攔截。
{
success: false,
mesg: "請求發(fā)生了運行時異常,請稍后再試"
}當我們在Controller層拋出IOException時,就會被exceptionHandler進行處理,然后攔截。
{
success: false,
mesg: "請求發(fā)生了異常,請稍后再試"
}SpringMVC為我們提供的Controller層異常處理真的是太方便了,尤其是@ExceptionHandler,推薦大家使用。
四、自定義異常處理類CustomUserException
4.1、自定義異常處理類
在程序中,可能會遇到JDK提供的任何標準異常類都無法充分描述清楚我們想要表達的問題,又或者捕捉數(shù)據(jù)庫異常時候不知道具體異常名字,這種情況下可以創(chuàng)建自己的異常類,即自定義異常類,在需要的時候手動throw出,然后再全局異常統(tǒng)一處理。
自定義異常類只需從Exception類或者它的子類派生一個子類即可。自定義異常類如果繼承Exception類,則為受檢查異常,必須對其進行處理;如果不想處理,可以讓自定義異常類繼承運行時異常RuntimeException類。習慣上,自定義異常類應(yīng)該包含2個構(gòu)造器:一個是默認的構(gòu)造器,另一個是帶有詳細信息的構(gòu)造器。
/**
* 自定義異常的步驟:
* (1)繼承 Exception 或 RuntimeException
* (2)定義構(gòu)造方法
*/
public class CustomUserException extends RuntimeException {
private Integer code;
public CustomUserException(UserResponseEnum userResponseEnum){
super(userResponseEnum.getDescription());
this.code = userResponseEnum.getCode();
}
public Integer getCode() {
return code;
}
}在構(gòu)造自定義業(yè)務(wù)異常對象時使用了枚舉的方式,將常見的業(yè)務(wù)錯誤提示語對應(yīng)的錯誤代碼進行映射,枚舉類如下所示:
public enum UserResponseEnum {
USER_NOT_FOUND(50001,"用戶不存在"),
USER_AUTHENTICATION_ERROR(50002,"用戶密碼不正確");
private Integer code;
private String description;
UserResponseEnum(Integer code, String description) {
this.code = code;
this.description = description;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}4.2、通過@ControllerAdvice和@ExceptionHandler注解,實現(xiàn)統(tǒng)一異常捕獲
@RestController
@ControllerAdvice(basePackages = {"com.hs.controller"})
public class CustomExceptionAdvice
{
private static final Logger logger = LoggerFactory.getLogger(CustomExceptionAdvice.class);
/**
* 處理與用戶相關(guān)的業(yè)務(wù)異常
* @return
*/
@ExceptionHandler(CustomUserException.class)
public BaseResult UserExceptionHandler(HttpServletRequest request,CustomUserException e){
logger.error("用戶信息異常:Host:{} invoke URL:{},錯誤信息:{}",request.getRemoteHost(),request.getRequestURL(),e.getMessage());
return new BaseResult(e.getCode(),false,e.getMessage());
}
}
4.3、在業(yè)務(wù)代碼中在需要拋出異常的地方拋出對應(yīng)的異常即可
/**
* 根據(jù)主鍵獲取用戶實體
* @param id
* @return
*/
public User selectById(String id)
{
User user = userMapper.selectByPrimaryId(id);
if (user == null) {
throw new CustomUserException(UserResponseEnum.USER_NOT_FOUND);
}
return user;
}
返回信息如下:
{
"code": 50001,
"data": false,
"message": "用戶不存在"
}
前端即可根據(jù)返回信息對用戶進行友好的提示。
到此這篇關(guān)于Spring中的@ControllerAdvice和@ExceptionHandler注解處理全局異常的文章就介紹到這了,更多相關(guān)@ControllerAdvice和@ExceptionHandler注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring中的@ExceptionHandler注解統(tǒng)一異常處理詳解
- SpringMVC使用@ExceptionHandler注解在Controller中處理異常
- Spring的異常處理@ExceptionHandler注解解析
- 關(guān)于SpringBoot使用@ExceptionHandler注解局部異常處理
- Spring中@ExceptionHandler注解的使用方式
- Spring中@ExceptionHandler注解的工作原理詳解
- Spring @ExceptionHandler注解統(tǒng)一異常處理和獲取方法名
- Spring中的@ExceptionHandler注解詳解與應(yīng)用示例
相關(guān)文章
Java String字符串和Unicode字符相互轉(zhuǎn)換代碼
這篇文章主要介紹了Java String字符串和Unicode字符相互轉(zhuǎn)換代碼,需要的朋友可以參考下2014-10-10
hibernate通過session實現(xiàn)增刪改查操作實例解析
這篇文章主要介紹了hibernate通過session實現(xiàn)增刪改查操作實例解析,具有一定借鑒價值,需要的朋友可以參考下。2017-12-12

