Spring中的@ControllerAdvice和@ExceptionHandler注解處理全局異常
前言
開發(fā)過(guò)程中,難免有的程序會(huì)因?yàn)槟承┰驋伋霎惓?,而這些異常一般都是利用try ,catch的方式處理異?;蛘遲hrow,throws的方式拋出異常不管。
這種方法對(duì)于程序員來(lái)說(shuō)處理也比較麻煩,對(duì)客戶來(lái)說(shuō)也不太友好,所以我們希望既能方便程序員編寫代碼,不用過(guò)多的自己去處理各種異常編寫重復(fù)的代碼又能提升用戶的體驗(yàn),這時(shí)候全局異常處理就顯得很重要也很便捷了。
一、@ControllerAdvice和@ExceptionHandler簡(jiǎn)介
在構(gòu)建RestFul接口的今天,我們一般會(huì)限定好返回?cái)?shù)據(jù)的格式,有利于前端調(diào)用解析,比如:
{ "code": 0, "data": {}, "msg": "操作成功" }
但有時(shí)卻往往會(huì)產(chǎn)生一些bug,這時(shí)候就破壞了返回?cái)?shù)據(jù)的一致性,導(dǎo)致調(diào)用者無(wú)法解析。所以我們常常會(huì)定義一個(gè)全局的異常攔截器。
1.1、@ControllerAdvice
@ControllerAdvice 是Spring 3.2提供的新注解,可以對(duì)Controller中使用到@RequestMapping注解的方法做邏輯處理。
@ControllerAdvice ,很多初學(xué)者可能都沒有聽說(shuō)過(guò)這個(gè)注解,實(shí)際上這是一個(gè)非常有用的注解。顧名思義,這是一個(gè)增強(qiáng)的 Controller,一般配合@ExceptionHandler使用來(lái)處理全局異常。注意不能自己try和catch異常,否則就不會(huì)被全局異常處理捕獲到。
使用這個(gè)注解 ,可以實(shí)現(xiàn)三個(gè)方面的功能:
- 全局異常處理
- 全局?jǐn)?shù)據(jù)綁定
- 全局?jǐn)?shù)據(jù)預(yù)處理
靈活使用這三個(gè)功能,可以幫助我們簡(jiǎn)化很多工作,需要注意的是,這是 SpringMVC 提供的功能,在 Spring Boot 中可以直接使用,這里只介紹全局異常處理,需要其他功能可以訪問(wèn)參考鏈接。
1.2、全局異常處理
Springboot對(duì)于全局異常的處理做了不錯(cuò)的支持,它提供了兩個(gè)可用的注解。
@ControllerAdvice:用來(lái)開啟全局的異常捕獲
@ExceptionHandler:說(shuō)明捕獲哪些異常,對(duì)哪些異常進(jìn)行處理。
使用 @ControllerAdvice結(jié)合@ExceptionHandler 實(shí)現(xiàn)全局異常處理,只需要定義類,添加該注解即可定義方式如下:
//可以使Spring自動(dòng)把要返回的對(duì)象轉(zhuǎn)化成json文本寫入到響應(yīng)體中,比如自定義的ResultBean @ResponseBody @ControllerAdvice public class MyGlobalExceptionHandler { // 專門用來(lái)捕獲和處理Controller層的異常 @ExceptionHandler(Exception.class) public ModelAndView customException(Exception e) { ModelAndView mv = new ModelAndView(); mv.addObject("message", e.getMessage()); mv.setViewName("myerror"); return mv; } // 專門用來(lái)捕獲和處理Controller層的空指針異常 @ExceptionHandler(NullPointerException.class) public ModelAndView nullPointerExceptionHandler(NullPointerException e) { ModelAndView mv = new ModelAndView(new MappingJackson2JsonView()); mv.addObject("success",false); mv.addObject("mesg","請(qǐng)求發(fā)生了空指針異常,請(qǐng)稍后再試"); return mv; } }
在該類中,可以定義多個(gè)方法,不同的方法處理不同的異常,例如專門處理空指針的方法、專門處理數(shù)組越界的方法...,也可以直接向上面代碼一樣,在一個(gè)方法中處理所有的異常信息。
@ExceptionHandler 注解用來(lái)指明異常的處理類型,即如果這里指定為 NullpointerException,則數(shù)組越界異常就不會(huì)進(jìn)到這個(gè)方法中來(lái)。
二、為什么要做Controller層的異常統(tǒng)一處理以及統(tǒng)一結(jié)果返回
不知道你平時(shí)在寫Controller層接口的時(shí)候,有沒有注意過(guò)拋出異常該怎么處理,是否第一反應(yīng)是想著用個(gè)try-catch來(lái)捕獲異常?但是這樣地處理只適合那種編譯器主動(dòng)提示的檢查時(shí)異常,因?yàn)槟悴挥胻ry-catch就過(guò)不了編譯檢查,所以你能主動(dòng)地抓獲異常并進(jìn)行處理。但是,如果存在運(yùn)行時(shí)異常且你沒有來(lái)得及想到去處理它的時(shí)候會(huì)發(fā)生什么呢?我們可以來(lái)先看看下面的這個(gè)沒有處理運(yùn)行時(shí)異常的例子:
@RestController public class ExceptionRest { @GetMapping("getNullPointerException") public Map<String,Object> getNullPointerException(){ throw new NullPointerException("出現(xiàn)了空指針異常"); } }
以上代碼在基于maven的SpringMVC項(xiàng)目中,使用tomcat啟動(dòng)后,瀏覽器端發(fā)起如下請(qǐng)求:
//localhost:8080/zxtest/getNullPointerException
訪問(wèn)后得到的結(jié)果是這樣的,瀏覽器收到的報(bào)錯(cuò)信息:
可以看到,我們?cè)贑ontroller接口層拋出了一個(gè)空指針異常,然后沒有捕獲,結(jié)果異常堆棧就會(huì)返回給前端瀏覽器,給用戶造成了非常不好的體驗(yàn)。
除此之外,前端從報(bào)錯(cuò)信息中能看到后臺(tái)系統(tǒng)使用的服務(wù)器及中間件類型、所采用的框架信息及類信息,甚至如果后端拋出的是SQL異常,那么還可以看到SQL異常的具體查詢的參數(shù)信息,這是一個(gè)中危安全漏洞,是必須要修復(fù)的。
三、使用@ExceptionHandler和@ControllerAdvice做到統(tǒng)一處理
當(dāng)出現(xiàn)這種運(yùn)行時(shí)異常的時(shí)候,我們想到的最簡(jiǎn)單的方法也許就是給可能會(huì)拋出異常的代碼加上異常處理,如下所示:
@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","請(qǐng)求發(fā)生異常,請(qǐng)稍后再試"); } return returnMap; } }
因?yàn)槲覀兪謩?dòng)地在拋出異常的地方加上了處理,并妥善地返回發(fā)生異常時(shí)該返回給前端的內(nèi)容,因此,當(dāng)我們?cè)俅卧跒g覽器發(fā)起相同的請(qǐng)求時(shí)得到就是以下內(nèi)容:
{ success: false, mesg: "請(qǐng)求發(fā)生異常,請(qǐng)稍后再試" }
貌似問(wèn)題得到了解決,但是你能確保你可以在所有可能會(huì)發(fā)生異常的地方都正好捕獲了異常并處理嗎?你能確保團(tuán)隊(duì)的其他人也這么做?
很明顯,你需要一個(gè)統(tǒng)一的異常捕獲與處理方案。
Spring3.2以后,SpringMVC引入了ExceptionHandler的處理方法,使得對(duì)異常的處理變得更加簡(jiǎn)單和精確,你唯一需要做的就是新建一個(gè)Controller,然后再里面加上兩個(gè)注解即可完成Controller層所有異常的捕獲與處理。
3.1、@ExceptionHandler和@ControllerAdvice基本使用
新建一個(gè)Controller如下:
@ControllerAdvice public class ExceptionConfigController { @ExceptionHandler public ModelAndView exceptionHandler(Exception e){ ModelAndView mv = new ModelAndView(new MappingJackson2JsonView()); mv.addObject("success",false); mv.addObject("mesg","請(qǐng)求發(fā)生了異常,請(qǐng)稍后再試"); return mv; } }
我們?cè)谌缟系拇a中,類上加了@ControllerAdvice注解,表示它是一個(gè)增強(qiáng)版的controller,然后在里面創(chuàng)建了一個(gè)返回ModelAndView對(duì)象的exceptionHandler方法,其上加上@ExceptionHandler注解,表示這是一個(gè)異常處理方法,然后在方法里面寫上具體的異常處理及返回參數(shù)邏輯即可,如此就完成了所有的工作,真的是太方便了。
我們?cè)跒g覽器發(fā)起調(diào)用后就返回了如下的結(jié)果:
{ success: false, mesg: "請(qǐng)求發(fā)生了異常,請(qǐng)稍后再試" }
3.2、@ExceptionHandler具體異常的處理
相比與HandlerExceptionResolver而言,使用@ExceptionHandler更能靈活地對(duì)不同的異常進(jìn)行分別的處理。并且,當(dāng)拋出的異常是指定異常的子類,那么照樣能夠被捕獲和處理。
我們改變下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。
我們?cè)贓xceptionConfigController做這樣的處理:
@ControllerAdvice public class ExceptionConfigController { // 專門用來(lái)捕獲和處理Controller層的空指針異常 @ExceptionHandler(NullPointerException.class) public ModelAndView nullPointerExceptionHandler(NullPointerException e){ ModelAndView mv = new ModelAndView(new MappingJackson2JsonView()); mv.addObject("success",false); mv.addObject("mesg","請(qǐng)求發(fā)生了空指針異常,請(qǐng)稍后再試"); return mv; } // 專門用來(lái)捕獲和處理Controller層的運(yùn)行時(shí)異常 @ExceptionHandler(RuntimeException.class) public ModelAndView runtimeExceptionHandler(RuntimeException e){ ModelAndView mv = new ModelAndView(new MappingJackson2JsonView()); mv.addObject("success",false); mv.addObject("mesg","請(qǐng)求發(fā)生了運(yùn)行時(shí)異常,請(qǐng)稍后再試"); return mv; } // 專門用來(lái)捕獲和處理Controller層的異常 @ExceptionHandler(Exception.class) public ModelAndView exceptionHandler(Exception e){ ModelAndView mv = new ModelAndView(new MappingJackson2JsonView()); mv.addObject("success",false); mv.addObject("mesg","請(qǐng)求發(fā)生了異常,請(qǐng)稍后再試"); return mv; } }
當(dāng)我們?cè)贑ontroller層拋出NullPointerException時(shí),就會(huì)被nullPointerExceptionHandler進(jìn)行處理,然后攔截。
{ success: false, mesg: "請(qǐng)求發(fā)生了空指針異常,請(qǐng)稍后再試" }
當(dāng)我們?cè)贑ontroller層拋出ClassCastException時(shí),就會(huì)被runtimeExceptionHandler進(jìn)行處理,然后攔截。
{ success: false, mesg: "請(qǐng)求發(fā)生了運(yùn)行時(shí)異常,請(qǐng)稍后再試" }
當(dāng)我們?cè)贑ontroller層拋出IOException時(shí),就會(huì)被exceptionHandler進(jìn)行處理,然后攔截。
{ success: false, mesg: "請(qǐng)求發(fā)生了異常,請(qǐng)稍后再試" }
SpringMVC為我們提供的Controller層異常處理真的是太方便了,尤其是@ExceptionHandler,推薦大家使用。
四、自定義異常處理類CustomUserException
4.1、自定義異常處理類
在程序中,可能會(huì)遇到JDK提供的任何標(biāo)準(zhǔn)異常類都無(wú)法充分描述清楚我們想要表達(dá)的問(wèn)題,又或者捕捉數(shù)據(jù)庫(kù)異常時(shí)候不知道具體異常名字,這種情況下可以創(chuàng)建自己的異常類,即自定義異常類,在需要的時(shí)候手動(dòng)throw出,然后再全局異常統(tǒng)一處理。
自定義異常類只需從Exception類或者它的子類派生一個(gè)子類即可。自定義異常類如果繼承Exception類,則為受檢查異常,必須對(duì)其進(jìn)行處理;如果不想處理,可以讓自定義異常類繼承運(yùn)行時(shí)異常RuntimeException類。習(xí)慣上,自定義異常類應(yīng)該包含2個(gè)構(gòu)造器:一個(gè)是默認(rèn)的構(gòu)造器,另一個(gè)是帶有詳細(xì)信息的構(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ù)異常對(duì)象時(shí)使用了枚舉的方式,將常見的業(yè)務(wù)錯(cuò)誤提示語(yǔ)對(duì)應(yīng)的錯(cuò)誤代碼進(jìn)行映射,枚舉類如下所示:
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、通過(guò)@ControllerAdvice和@ExceptionHandler注解,實(shí)現(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:{},錯(cuò)誤信息:{}",request.getRemoteHost(),request.getRequestURL(),e.getMessage()); return new BaseResult(e.getCode(),false,e.getMessage()); } }
4.3、在業(yè)務(wù)代碼中在需要拋出異常的地方拋出對(duì)應(yīng)的異常即可
/** * 根據(jù)主鍵獲取用戶實(shí)體 * @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ù)返回信息對(duì)用戶進(jìn)行友好的提示。
到此這篇關(guān)于Spring中的@ControllerAdvice和@ExceptionHandler注解處理全局異常的文章就介紹到這了,更多相關(guān)@ControllerAdvice和@ExceptionHandler注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(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-10hibernate通過(guò)session實(shí)現(xiàn)增刪改查操作實(shí)例解析
這篇文章主要介紹了hibernate通過(guò)session實(shí)現(xiàn)增刪改查操作實(shí)例解析,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12詳談spring boot中幾種常見的依賴注入問(wèn)題
這篇文章主要介紹了spring boot中幾種常見的依賴注入問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Java多線程實(shí)戰(zhàn)之單例模式與多線程的實(shí)例詳解
今天小編就為大家分享一篇關(guān)于Java多線程實(shí)戰(zhàn)之單例模式與多線程的實(shí)例詳解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02Java實(shí)現(xiàn)班級(jí)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)班級(jí)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02