從零搭建后端框架——異常統(tǒng)一處理的三種方式
前言
項目在運行時出現(xiàn)異常時,如若沒有對異常進行捕獲并處理,就會出現(xiàn)如下頁面:

這樣顯然對用戶是極其不友好的。
后端不應該直接返回錯誤頁面,而應返回統(tǒng)一的錯誤信息,比如:
- {
- "code": 500,
- "data": null,
- "message": "服務異常,請稍后重試"
- }
- 復制代碼
然后,前端根據(jù)返回的信息,顯示友好的提示頁面。
Spring提供了三種方式對異常統(tǒng)一處理:
- @ExceptionHandler
- 實現(xiàn)HandlerExceptionResolver接口
- @ControllerAdvice + @ExceptionHandler
下面我們來實際操作下。
具體實現(xiàn)
@ExceptionHandler
在【統(tǒng)一基類、接口、返回對象設計】這一篇文章中, 定義了Controller的基類BaseController,所以只要在BaseController中使用@ExceptionHandler處理異常, 其它Controller繼承BaseController即可。實現(xiàn)如下:
- @Slf4j
- public abstract class BaseController {
- /**
- * BusinessException 異常處理
- */
- @ResponseBody
- @ExceptionHandler(BusinessException.class)
- public ApiResult businessExceptionHandler(BusinessException e) {
- log.error(e.getMessage(), e);
- // do something
- return ApiResult.fail(e.getMessage());
- }
- /**
- * Exception 異常處理
- */
- @ResponseBody
- @ExceptionHandler(Exception.class)
- public ApiResult exceptionHandler(Exception e) {
- log.error(e.getMessage(), e);
- return ApiResult.fail("服務異常,請稍后重試");
- }
- }
- 復制代碼
這里對異常BusinessException和Exception進行了處理, BusinessException是約定的業(yè)務異常的基類,若是主動拋出一般都要求是BusinessException的子類,都會被businessExceptionHandler處理。 若是其它異常,可能是意想不到的異常,則會被exceptionHandler處理。
統(tǒng)一處理后,返回結果如下:

實現(xiàn)HandlerExceptionResolver接口
- @Slf4j
- @Component
- public class GlobalHandlerExceptionResolver implements HandlerExceptionResolver {
-
- @Override
- public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) {
- log.error(e.getMessage(), e);
- ApiResult apiResult;
- if (e instanceof BusinessException) {
- BusinessException be = (BusinessException) e;
- // do something
- apiResult = ApiResult.fail(be.getMessage());
- } else {
- apiResult = ApiResult.fail("服務異常,請稍后重試");
- }
- WebUtils.writeJson(response, apiResult);
- return null;
- }
- }
- 復制代碼
- @Slf4j
- @Component
- public class GlobalHandlerExceptionResolver implements HandlerExceptionResolver {
- @Override
- public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) {
- log.error(e.getMessage(), e);
- ApiResult apiResult;
- if (e instanceof BusinessException) {
- BusinessException be = (BusinessException) e;
- // do something
- apiResult = ApiResult.fail(be.getMessage());
- } else {
- apiResult = ApiResult.fail("服務異常,請稍后重試");
- }
- WebUtils.writeJson(response, apiResult);
- return null;
- }
- }
- 復制代碼
該方式需要實現(xiàn)HandlerExceptionResolver接口,然后將實現(xiàn)類注入到Spring容器中。
但第一種方式中,通過@ResponseBody注解,Spring就幫我們返回了json格式數(shù)據(jù),而這需要自己實現(xiàn)。
這里實現(xiàn)了工具類WebUtils,用于返回json數(shù)據(jù),如下:
- public class WebUtils {
- private static final Logger log = LoggerFactory.getLogger(WebUtils.class);
- private static Gson gson = new GsonBuilder().serializeNulls().create();
- /**
- * 返回json數(shù)據(jù)
- *
- * @param response
- * @param object
- */
- public static void writeJson(HttpServletResponse response, int status, Object object) {
- response.setHeader("Content-Type", "application/json;charset=UTF-8");
- response.setContentType("application/json;charset=UTF-8");
- response.setStatus(status);
- PrintWriter out = null;
- try {
- String data = object instanceof String ? (String) object : gson.toJson(object);
- out = response.getWriter();
- out.print(data);
- out.flush();
- } catch (Exception e) {
- log.error(e.getMessage(), e);
- } finally {
- if (out != null) {
- out.close();
- }
- }
- }
- /**
- * 返回json數(shù)據(jù)
- *
- * @param response
- * @param object
- */
- public static void writeJson(HttpServletResponse response, Object object) {
- writeJson(response, HttpServletResponse.SC_OK, object);
- }
- /**
- * 返回json數(shù)據(jù)
- *
- * @param response
- * @param object
- */
- public static void writeJson(ServletResponse response, Object object) {
- if (response instanceof HttpServletResponse) {
- writeJson((HttpServletResponse) response, object);
- }
- }
- }
- 復制代碼
工具類中使用了Gson,需要引用:
- <dependency>
- <groupId>com.google.code.gson</groupId>
- <artifactId>gson</artifactId>
- </dependency>
- 復制代碼
@ControllerAdvice + @ExceptionHandler
該方式與第一種方式類似,如下:
- @Slf4j
- @ControllerAdvice
- public class GlobalExceptionHandler {
- /**
- * BusinessException 異常處理
- */
- @ResponseBody
- @ExceptionHandler(BusinessException.class)
- public ApiResult businessExceptionHandler(BusinessException e) {
- log.error(e.getMessage(), e);
- // do something
- return ApiResult.fail(e.getMessage());
- }
- /**
- * Exception 異常處理
- */
- @ResponseBody
- @ExceptionHandler(Exception.class)
- public ApiResult exceptionHandler(Exception e) {
- log.error(e.getMessage(), e);
- return ApiResult.fail("服務異常,請稍后重試");
- }
- }
- 復制代碼
總結
三種方式都能很好對異常進行統(tǒng)一處理,但是一般推薦使用@ControllerAdvice + @ExceptionHandler方式, 這樣能夠使異常處理與業(yè)務邏輯分離,并且不用自己處理Json數(shù)據(jù)返回。
源碼
github.com/zhuqianchan…