(六)从零搭建后端框架——异常统一处理的三种方式

1,598 阅读2分钟

前言

项目在运行时出现异常时,如若没有对异常进行捕获并处理,就会出现如下页面:

异常页面

这样显然对用户是及其不友好的。

后端不应该直接返回错误页面,而应返回统一的错误信息,比如:

{
  "code": 500,
  "data": null,
  "message": "服务异常,请稍后重试"
}

然后,前端根据返回的信息,显示友好的提示页面。

Spring提供了三种方式对异常统一处理:

  1. @ExceptionHandler
  2. 实现HandlerExceptionResolver接口
  3. @ControllerAdvice + @ExceptionHandler

下面我们来实际操作下。

具体实现

@ExceptionHandler

【统一基类、接口、返回对象设计】这一篇文章中, 定义了Controller的基类BaseController,所以只要在BaseController中使用@ExceptionHandler处理异常, 其它Controller继承BaseController即可。实现如下:

@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是约定的业务异常的基类,若是主动抛出一般都要求是BusinessException的子类,都会被businessExceptionHandler处理。 若是其它异常,可能是意想不到的异常,则会被exceptionHandler处理。

统一处理后,返回结果如下:

异常页面

实现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;
    }
}

该方式需要实现HandlerExceptionResolver接口,然后将实现类注入到Spring容器中。

但第一种方式中,通过@ResponseBody注解,Spring就帮我们返回了json格式数据,而这需要自己实现。

这里实现了工具类WebUtils,用于返回json数据,如下:

public class WebUtils {

    private static final Logger log = LoggerFactory.getLogger(WebUtils.class);

    private static Gson gson = new GsonBuilder().serializeNulls().create();

    /**
     * 返回json数据
     *
     * @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数据
     *
     * @param response
     * @param object
     */
    public static void writeJson(HttpServletResponse response, Object object) {
        writeJson(response, HttpServletResponse.SC_OK, object);
    }

    /**
     * 返回json数据
     *
     * @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("服务异常,请稍后重试");
    }
}

总结

三种方式都能很好对异常进行统一处理,但是一般推荐使用@ControllerAdvice + @ExceptionHandler方式, 这样能够使异常处理与业务逻辑分离,并且不用自己处理Json数据返回。

源码

github.com/zhuqianchan…

往期回顾