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