https://juejin.im/post/5e47f5a8e51d4526ff02484ahtml
本篇主要是記錄如何使用 SpringBoot 所提供的 ErrorController 這個接口能力;其內置了一個 BasicErrorController 對異常進行統一的處理,當在 Controller 發生異常的時候會自動把請求 forward 到 /error 這個請求 path 下(/error 是 SpringBoot 提供的一個默認的mapping)。BasicErrorController 提供兩種返回錯誤:一、頁面返回;二、json 返回。java
背景
開發中遇到的一個問題:項目中全部的 rest 請求均是經過 json 形式返回,且自定義了一個統一的數據結構對象,以下:json
public class Response<T> {
// 數據
private T data;
// success 標記
private boolean success;
// 異常信息
private String error;
// 省略 get set
}
複製代碼
這個結構很是常見,相信不少開發者都這麼玩過。項目中 rest 請求返回的全部結果都是以 Response 對象形式返回,以下:安全
@RequestMapping("test")
public Response<String> testApi(){
Response<String> result = new Response<>();
result.setData("this is glmapper blog");
result.setSuccess(true);
return result;
}
複製代碼
這基本是最簡化版的一個模型;出於安全考慮,如今有個需求是須要對每一個請求作校驗,好比校驗請求中是否攜帶 token 這種。思路很簡單就是經過攔截器或者過濾器的方式來對請求作攔截檢驗。數據結構
其實不論是攔截器仍是過濾器,須要考慮的一個問題是,在校驗不經過或者校驗時產生異常的狀況下,怎麼把異常信息以項目中規定的統一數據格式返回,即返回 Response。app
直接將 Response 寫回去
利用 ServletResponse 中提供的 PrintWriter,將 Response 以 json 格式直接 print 回去。大概代碼以下:ide
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String requestURI = request.getRequestURI();
// mock 測試異常請求
if (requestURI.contains("testTokenError")) {
Response<String> response = new Response<>();
response.setError("token validation fails");
// 回寫異常信息
returnResponse((HttpServletResponse)servletResponse,JSONObject.toJSONString(response));
// 返回
return;
}
chain.doFilter(servletRequest, servletResponse);
}
private void returnResponse(HttpServletResponse response, String data) {
PrintWriter writer = null;
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=utf-8");
try {
writer = response.getWriter();
// 經過 PrintWriter 將 data 數據直接 print 回去
writer.print(data);
} catch (IOException e) {
} finally {
if (writer != null)
writer.close();
}
}
複製代碼
這種方式比較簡單和直接,print 異常數據以後直接 return,再也不繼續過濾器鏈。post
拋出異常,經過 BasicErrorController 方式處理
這種方式是利用了 SpringBoot 自己提供的能力,能夠更優雅的處理錯誤信息。代碼大體以下:測試
一、是在 Filter 中就直接拋出一個異常this
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String requestURI = request.getRequestURI();
// mock 測試異常請求
if (requestURI.contains("testTokenError")) {
// 直接返回一個自定義的異常
throw new ValidationException("token validation fails");
}
chain.doFilter(servletRequest, servletResponse);
}
複製代碼
二、定義一個異常處理的 Controller
這裏定義一個 TokenErrorController ,繼承自 SpringBoot 提供的 BasicErrorController 這個類,而後重寫 error 這個方法(若是是頁面的話,重寫 errorHtml 這個方法),用於返回自定義的 Response 數據。代碼以下:
@RestController
public class TokenErrorController extends BasicErrorController {
// 重寫 error 方法
@Override
@RequestMapping(produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
// 拿到 body 中的異常 message
String message = body.get("message").toString();
// 構建 Response 對象
Response response = new Response();
// 將 message 的 設置到 response
response.setError(message);
// 返回
return new ResponseEntity(response, status);
}
// 省略其餘無關代碼
}
複製代碼
這樣就能夠實如今不改動以前工程任何代碼的狀況下只處理額外 Filter 中拋出的異常了。須要注意的是,上述是經過 BasicErrorController 來接受了 Filter 拋出的異常信息,而後再經過 BasicErrorController 將異常信息進行包裝而且返回。爲何要提一下這個呢?主要是爲了和 SpringBoot 中基於 REST 請求層所提供的兩個用於處理全局異常的註解區分,這兩個註解分別是 @ControllerAdvice 和 @RestControllerAdvice,經過註解的名字其實就能看出,SpringBoot 中,能夠經過這兩個註解來實現對 @Controller 和 @RestController 標註的類進行全局攔截,由於是 Controller 層面的 AOP 攔截,因此對於 Filter 中拋出的異常,經過 @ControllerAdvice 和 @RestControllerAdvice 兩個註解定義的全局異常處理器是無法處理的。
下面就簡單介紹下 @ControllerAdvice 和 @RestControllerAdvice 這兩個註解的使用。
全局異常處理
自定義一個 OtherExcepetion ,而後再使用基於 @RestControllerAdvice 註解編寫一個全局異常處理器。
@RestControllerAdvice
public class OtherExceptionHandler {
// 這裏只處理 OtherException 異常類型
@ExceptionHandler(value = OtherException.class)
public Response<String> otherExceptionHandler(HttpServletRequest req, OtherException e){
Response response = new Response();
response.setError(e.getMessage());
return response;
}
// 固然你也能夠定義處理其餘異常的 @ExceptionHandler
}
複製代碼
這種方式是無法處理 Filter 中異常的,只能處理 Controller 裏面拋出的異常。
小結
本篇主要記錄了在 SpringBoot 中如何保證 Filter 中拋出的異常能和業務同樣以指定類型的對象返回,並對 SpringBoot 中提供的基於 Controller 層異常捕獲處理進行簡單介紹。二者處理異常的思路是不一樣的:
- BasicErrorController:接受來自 /error 的異常請求處理,Filter 中拋出的異常先 forward 到 /error,而後處理。
- @RestControllerAdvice:經過對於全部 @Controller 註解所標註的類進行 AOP 攔截,可以根據異常類型匹配具體的 ExceptionHandler 進行處理。
水平有限,文章若是表述錯誤的地方,但願各位大佬給予指正~