java异常统一处理,Controller层的异常统一处理及返回
Controller層的異常統(tǒng)一處理及返回
一、為什么要做這件事?
不知道你平時(shí)在寫Controller層接口的時(shí)候,有沒有注意過拋出異常該怎么處理,是否第一反應(yīng)是想著用個(gè)try-catch來捕獲異常?
但是這樣地處理只適合那種編譯器主動(dòng)提示的檢查時(shí)異常,因?yàn)槟悴挥胻ry-catch就過不了編譯檢查,所以你能主動(dòng)地抓獲異常并進(jìn)行處理。但是,如果存在運(yùn)行時(shí)異常且你沒有來得及想到去處理它的時(shí)候會(huì)發(fā)生什么呢?我們可以來先看看下面的這個(gè)沒有處理運(yùn)行時(shí)異常的例子:
@RestController
public class ExceptionRest {
@GetMapping("getNullPointerException")
public Map getNullPointerException(){
throw new NullPointerException("出現(xiàn)了空指針異常");
}
}
以上代碼在基于maven的SpringMVC項(xiàng)目中,使用tomcat啟動(dòng)后,瀏覽器端發(fā)起如下請求:
http://localhost:8080/zxtest/getNullPointerException
訪問后得到的結(jié)果是這樣的:
瀏覽器收到的報(bào)錯(cuò)信息
可以看到,我們在Controller接口層拋出了一個(gè)空指針異常,然后沒有捕獲,結(jié)果異常堆棧就會(huì)返回給前端瀏覽器,給用戶造成了非常不好的體驗(yàn)。
除此之外,前端從報(bào)錯(cuò)信息中能看到后臺系統(tǒng)使用的服務(wù)器及中間件類型、所采用的框架信息及類信息,甚至如果后端拋出的是SQL異常,那么還可以看到SQL異常的具體查詢的參數(shù)信息,這是一個(gè)中危安全漏洞,是必須要修復(fù)的。
PS:上面的項(xiàng)目如果使用SpringBoot的話可能在前端得不到報(bào)錯(cuò)信息,因?yàn)镾pringBoot自動(dòng)對返回的報(bào)錯(cuò)內(nèi)容做了處理,我們需要使用Maven的web模板創(chuàng)建一個(gè)只包含SpringMVC的項(xiàng)目來復(fù)現(xiàn)以上場景。
二、如何做到統(tǒng)一處理?
當(dāng)出現(xiàn)這種運(yùn)行時(shí)異常的時(shí)候,我們想到的最簡單的方法也許就是給可能會(huì)拋出異常的代碼加上異常處理,如下所示:
@RestController
public class ExceptionRest {
private Logger log = LoggerFactory.getLogger(ExceptionRest.class);
@GetMapping("getNullPointerException")
public Map getNullPointerException(){
Map returnMap = new HashMap();
try{
throw new NullPointerException("出現(xiàn)了空指針異常");
}catch(NullPointerException e){
log.error("出現(xiàn)了空指針異常",e);
returnMap.put("success",false);
returnMap.put("mesg","請求發(fā)生異常,請稍后再試");
}
return returnMap;
}
}
因?yàn)槲覀兪謩?dòng)地在拋出異常的地方加上了處理,并妥善地返回發(fā)生異常時(shí)該返回給前端的內(nèi)容,因此,當(dāng)我們再次在瀏覽器發(fā)起相同的請求時(shí)得到就是以下內(nèi)容:
{
success: false,
mesg: "請求發(fā)生異常,請稍后再試"
}
貌似問題得到了解決,但是你能確保你可以在所有可能會(huì)發(fā)生異常的地方都正好捕獲了異常并處理嗎?你能確保團(tuán)隊(duì)的其他人也這么做?
很明顯,你需要一個(gè)統(tǒng)一的異常捕獲與處理方案。
2.1 使用HandlerExceptionResolver
HandlerExceptionResolver是一個(gè)異常處理接口,實(shí)現(xiàn)它的類在spring配置文件中注冊后就能捕獲Controller層拋出的所有異常,我們就是基于此來實(shí)現(xiàn)統(tǒng)一Web異常的處理和返回結(jié)果的配置。
2.1.1 基本使用
方式一:使用HttpServletResponse返回JSON信息
@Controller
public class WebExceptionResolver implements HandlerExceptionResolver {
private Logger log = LoggerFactory.getLogger(WebExceptionResolver.class);
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
log.error("請求{}發(fā)生異常!",httpServletRequest.getRequestURI());
ModelAndView mv = new ModelAndView();
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.setCharacterEncoding("UTF-8");
String retStr = "{\"success\":false,\"msg\":\"請求異常,請稍后再試\"}";
try{
httpServletResponse.getWriter().write(retStr);
}catch(Exception ex){
log.error("WebExceptionResolver處理異常",ex);
}
// 需要返回空的ModelAndView以阻止異常繼續(xù)被其它處理器捕獲
return mv;
}
}
通過以上的處理,所有Web層的異常都能被WebExceptionResolver捕獲并在resolveException中進(jìn)行處理,然后可以使用HttpServletResponse來統(tǒng)一返回想返回的信息。如下是請求相同的鏈接時(shí)返回給瀏覽器的內(nèi)容:
攔截Web異常后的返回信息
方式二:使用ModelAndView返回JSON信息
@Controller
public class WebExceptionResolver implements HandlerExceptionResolver {
private Logger log = LoggerFactory.getLogger(WebExceptionResolver.class);
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
log.error("請求{}發(fā)生異常!",httpServletRequest.getRequestURI());
ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
mv.addObject("success","false");
mv.addObject("mesg","請求異常,請稍后再試");
return mv;
}
}
這兩種方式效果等同,唯一的區(qū)別是,使用第二種方式需要多引入jackson-databind包。
com.fasterxml.jackson.core
jackson-databind
2.1.2 具體異常的處理
有時(shí)候我們需要在發(fā)生特定異常的時(shí)候做一些處理,那么只需要判斷捕獲的異常類型進(jìn)行分別處理即可:
@Controller
public class WebExceptionResolver implements HandlerExceptionResolver {
private Logger log = LoggerFactory.getLogger(WebExceptionResolver.class);
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
log.error("請求{}發(fā)生異常!",httpServletRequest.getRequestURI());
ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
mv.addObject("success","false");
if(e instanceof NullPointerException){
mv.addObject("mesg","請求發(fā)生了空指針異常,請稍后再試");
}else if(e instanceof ClassCastException){
mv.addObject("mesg","請求發(fā)生了類型轉(zhuǎn)換異常,請稍后再試");
}else{
mv.addObject("mesg","請求發(fā)生異常,請稍后再試");
}
return mv;
}
}
2.1.3 異常處理鏈
如果存在多個(gè)實(shí)現(xiàn)HandlerExceptionResolver的異常處理類,那么它們就會(huì)形成一個(gè)處理鏈,此時(shí)需要在spring配置文件中聲明哪個(gè)處理在前,哪個(gè)處理在后,越是在前面聲明的處理類就越是可以先對異常處理,甚至可以攔截異常,不再被后續(xù)的處理器處理。
PS:如果打算在當(dāng)前的異常處理器中攔截異常,防止繼續(xù)往外拋出被別的處理器處理,那么直接在最后返回一個(gè)空的ModelAndView對象即可。如果打算不攔截這個(gè)異常,繼續(xù)讓別的處理器處理的話,就返回null即可。
// 需要返回空的ModelAndView以阻止異常繼續(xù)被其它處理器捕獲
return mv;
// 返回null將不會(huì)攔截異常,其它處理器可以繼續(xù)處理該異常
return null;
2.2 使用@ExceptionHandler
Spring3.2以后,SpringMVC引入了ExceptionHandler的處理方法,使得對異常的處理變得更加簡單和精確,你唯一需要做的就是新建一個(gè)Controller,然后再里面加上兩個(gè)注解即可完成Controller層所有異常的捕獲與處理。
2.2.1基本使用
新建一個(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","請求發(fā)生了異常,請稍后再試");
return mv;
}
}
我們在如上的代碼中,類上加了@ControllerAdvice注解,表示它是一個(gè)增強(qiáng)版的controller,然后在里面創(chuàng)建了一個(gè)返回ModelAndView對象的exceptionHandler方法,其上加上@ExceptionHandler注解,表示這是一個(gè)異常處理方法,然后在方法里面寫上具體的異常處理及返回參數(shù)邏輯即可,如此就完成了所有的工作,真的是太方便了。
我們在瀏覽器發(fā)起調(diào)用后就返回了如下的結(jié)果:
{
success: false,
mesg: "請求發(fā)生了異常,請稍后再試"
}
2.2 具體異常的處理
相比與HandlerExceptionResolver而言,使用@ExceptionHandler更能靈活地對不同的異常進(jìn)行分別的處理。并且,當(dāng)拋出的異常是指定異常的子類,那么照樣能夠被捕獲和處理。
我們改變下controller層的代碼如下:
@RestController
public class ExceptionController {
@GetMapping("getNullPointerException")
public Map getNullPointerException() {
throw new NullPointerException("出現(xiàn)了空指針異常");
}
@GetMapping("getClassCastException")
public Map getClassCastException() {
throw new ClassCastException("出現(xiàn)了類型轉(zhuǎn)換異常");
}
@GetMapping("getIOException")
public Map 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層的運(yùn)行時(shí)異常
@ExceptionHandler(RuntimeException.class)
public ModelAndView runtimeExceptionHandler(RuntimeException e){
ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
mv.addObject("success",false);
mv.addObject("mesg","請求發(fā)生了運(yùn)行時(shí)異常,請稍后再試");
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;
}
}
那么
當(dāng)我們在Controller層拋出NullPointerException時(shí),就會(huì)被nullPointerExceptionHandler進(jìn)行處理,然后攔截。
{
success: false,
mesg: "請求發(fā)生了空指針異常,請稍后再試"
}
當(dāng)我們在Controller層拋出ClassCastException時(shí),就會(huì)被runtimeExceptionHandler進(jìn)行處理,然后攔截。
{
success: false,
mesg: "請求發(fā)生了運(yùn)行時(shí)異常,請稍后再試"
}
當(dāng)我們在Controller層拋出IOException時(shí),就會(huì)被exceptionHandler進(jìn)行處理,然后攔截。
{
success: false,
mesg: "請求發(fā)生了異常,請稍后再試"
}
三、總結(jié)
SpringMVC為我們提供的Controller層異常處理真的是太方便了,尤其是@ExceptionHandler,推薦大家使用。
本文完。
總結(jié)
以上是生活随笔為你收集整理的java异常统一处理,Controller层的异常统一处理及返回的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java在dog中定义name变量,组合
- 下一篇: JAVA单字节读取,java资料读取。(