在一個web項目開發(fā)中,通常都會涉及到Html和Json請求。當出現(xiàn)異常的時候,我們需要根據(jù)請求類型返回不同的信息。如果是Json請求,那么就返回
String或者ReponseEntity類型;如果是html請求,就要返回ModelAndView的錯誤頁面。
我們當然可以對Controller的每個接口方法拋出的異常單獨處理。但這樣做會導致大量的重復工作。Spring MVC為我們提供了@ControllerAdvice和@ExceptionHandler`兩個注解來實現(xiàn)全局的異常處理。關(guān)于這兩個注解的用法可以參考這里。
這解決了大部分的問題,但是如果同一個Controller中既有html又有json接口方法怎么辦呢?我們當然可以拆分成兩個Controller,一個包含html接口,另一個包含json接口。但是這樣做不夠靈活,而且我更習慣根據(jù)業(yè)務邏輯來歸類接口。有沒有更好的方法呢?
其實在寫異常處理方法時,我們可以將請求信息作為參數(shù)傳入,并根據(jù)請求類型來返回不同的數(shù)據(jù)。
public class BaseController{
private Boolean isJson(HttpServletRequest request){
String header = request.getHeader("content-type");
return header != null && header.contains("json");
}
@Override
@ExceptionHandler(BaseException.class)
public Object handleBaseException(HttpServletRequest request, baseException e) {
if(isJson(request)) {
return ResponseUtils.restResponse(
e.getCode(),
e.getMessage(),
e.getStatus()
);
} else {
ModelAndView modelAndView = initModelAndView();
if (e.getCode().equalsIgnoreCase("login_first")) {
modelAndView.setViewName("redirect:/list");
}
if (e.getCode().equalsIgnoreCase("real_name_not_set")) {
modelAndView.setViewName("redirect:/account");
}else{
modelAndView.setViewName("/404");
}
modelAndView.addObject("exception", e);
return modelAndView;
}
}
}
這里我們寫了一個BaseController,并在Controller中實現(xiàn)了異常捕獲的邏輯。isJson()通過判斷請求的Content-Type是否包含json字符串來判斷該請求類型。當然,更好更合適的方式是通過包頭中的Accept中的信息類判斷。需要注意的是handleBaseException()方法返回了Object類型,這樣我們就可以根據(jù)需要返回不同類型的數(shù)據(jù)了。以后只要Contrller繼承BaseController就不用再考慮異常的問題了。
但是,如果異常是在進入接口方法之前被拋出的呢。比如404,406錯誤,根本不會執(zhí)行接口方法,因此也無法被ExceptionHandler捕獲。這部分異常如何處理呢?
Spring Boot提供了一個統(tǒng)一的/error地址用于所有未被捕獲的異常拋出。默認設(shè)置下顯示的是一個whitelabel error page。

通過實現(xiàn)ErrorController,我們可以定制這個錯誤頁面。
@Controller
public class MpErrorController extends BaseController implements ErrorController {
private static final String PATH = "/error";
@RequestMapping(value = PATH)
public Object error() throws Exception {
throw new BaseException();
}
@Override
public String getErrorPath() {
return PATH;
}
}
我們希望這個error頁面也根據(jù)請求的類型做出不同的邏輯處理。因此,可以直接在error()拋出BaseException異常,并且讓這個Controller繼承于BaseController。這樣,被拋出的異常也會被handleBaseException()捕獲了。
至此,我們比較優(yōu)雅的實現(xiàn)了全局的異常處理。所有的BaseException異常處理邏輯都集中在handleBaseException()方法中。