​SpringCloud统一异常处理

简介: 我是小假 期待与你的下一次相遇 ~

默认异常处理

使用 AJAX 方式请求时返回的 JSON 格式错误信息。

  1. {
  2.    "timestamp": "2018-12-18T01:50:51.196+0000",
  3.    "status": 404,
  4.    "error": "Not Found",
  5.    "message": "No handler found for GET /err404",
  6.    "path": "/err404"
  7. }

使用浏览器请求时返回的错误信息界面。

自定义异常处理

引入依赖

  1. <dependency>
  2.    <groupId>com.alibaba</groupId>
  3.    <artifactId>fastjson</artifactId>
  4.    <version>1.2.54</version>
  5. </dependency>
  6. <dependency>
  7.    <groupId>org.springframework.boot</groupId>
  8.    <artifactId>spring-boot-starter-freemarker</artifactId>
  9. </dependency>

fastjson 是 JSON 序列化依赖, spring-boot-starter-freemarker 是一个模板引擎,用于我们设置错误输出模板。

增加配置

properties

  1. # 出现错误时, 直接抛出异常(便于异常统一处理,否则捕获不到404)
  2. spring.mvc.throw-exception-if-no-handler-found=true
  3. # 不要为工程中的资源文件建立映射
  4. spring.resources.add-mappings=false

yml

  1. spring:
  2.  # 出现错误时, 直接抛出异常(便于异常统一处理,否则捕获不到404)
  3.  mvc:
  4.    throw-exception-if-no-handler-found: true
  5.  # 不要为工程中的资源文件建立映射
  6.  resources:
  7.    add-mappings: false

新建错误信息实体

  1. /**
  2. * 信息实体
  3. */
  4. public class ExceptionEntity implements Serializable {
  5.    private static final long serialVersionUID = 1L;
  6.    private String message;
  7.    private int    code;
  8.    private String error;
  9.    private String path;
  10.    @JSONField(format = "yyyy-MM-dd hh:mm:ss")
  11.    private Date timestamp = new Date();
  12.    public static long getSerialVersionUID() {
  13.        return serialVersionUID;
  14.    }
  15.    public String getMessage() {
  16.        return message;
  17.    }
  18.    public void setMessage(String message) {
  19.        this.message = message;
  20.    }
  21.    public int getCode() {
  22.        return code;
  23.    }
  24.    public void setCode(int code) {
  25.        this.code = code;
  26.    }
  27.    public String getError() {
  28.        return error;
  29.    }
  30.    public void setError(String error) {
  31.        this.error = error;
  32.    }
  33.    public String getPath() {
  34.        return path;
  35.    }
  36.    public void setPath(String path) {
  37.        this.path = path;
  38.    }
  39.    public Date getTimestamp() {
  40.        return timestamp;
  41.    }
  42.    public void setTimestamp(Date timestamp) {
  43.        this.timestamp = timestamp;
  44.    }
  45. }

新建自定义异常

  1. /**
  2. * 自定义异常
  3. */
  4. public class BasicException extends RuntimeException {
  5.    private static final long serialVersionUID = 1L;
  6.    private int code = 0;
  7.    public BasicException(int code, String message) {
  8.        super(message);
  9.        this.code = code;
  10.    }
  11.    public int getCode() {
  12.        return this.code;
  13.    }
  14. }
  15. /**
  16. * 业务异常
  17. */
  18. public class BusinessException extends BasicException {
  19.    private static final long serialVersionUID = 1L;
  20.    public BusinessException(int code, String message) {
  21.        super(code, message);
  22.    }
  23. }

BasicException 继承了 RuntimeException ,并在原有的 Message 基础上增加了错误码 code 的内容。而 BusinessException 则是在业务中具体使用的自定义异常类,起到了对不同的异常信息进行分类的作用。

新建 error.ftl 模板文件

位置:/src/main/resources/templates/ 用于显示错误信息

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.    <meta name="robots" content="noindex,nofollow" />
  5.    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
  6.    <style>
  7.        h2{
  8.            color: #4288ce;
  9.            font-weight: 400;
  10.            padding: 6px 0;
  11.            margin: 6px 0 0;
  12.            font-size: 18px;
  13.            border-bottom: 1px solid #eee;
  14.        }
  15.        /* Exception Variables */
  16.        .exception-var table{
  17.            width: 100%;
  18.            max-width: 500px;
  19.            margin: 12px 0;
  20.            box-sizing: border-box;
  21.            table-layout:fixed;
  22.            word-wrap:break-word;
  23.        }
  24.        .exception-var table caption{
  25.            text-align: left;
  26.            font-size: 16px;
  27.            font-weight: bold;
  28.            padding: 6px 0;
  29.        }
  30.        .exception-var table caption small{
  31.            font-weight: 300;
  32.            display: inline-block;
  33.            margin-left: 10px;
  34.            color: #ccc;
  35.        }
  36.        .exception-var table tbody{
  37.            font-size: 13px;
  38.            font-family: Consolas,"Liberation Mono",Courier,"微软雅黑";
  39.        }
  40.        .exception-var table td{
  41.            padding: 0 6px;
  42.            vertical-align: top;
  43.            word-break: break-all;
  44.        }
  45.        .exception-var table td:first-child{
  46.            width: 28%;
  47.            font-weight: bold;
  48.            white-space: nowrap;
  49.        }
  50.        .exception-var table td pre{
  51.            margin: 0;
  52.        }
  53.    </style>
  54. </head>
  55. <body>
  56. <div class="exception-var">
  57.    <h2>Exception Datas</h2>
  58.    <table>
  59.        <tbody>
  60.        <tr>
  61.            <td>Code</td>
  62.            <td>
  63.                ${(exception.code)!}
  64.            </td>
  65.        </tr>
  66.        <tr>
  67.            <td>Time</td>
  68.            <td>
  69.                ${(exception.timestamp?datetime)!}
  70.            </td>
  71.        </tr>
  72.        <tr>
  73.            <td>Path</td>
  74.            <td>
  75.                ${(exception.path)!}
  76.            </td>
  77.        </tr>
  78.        <tr>
  79.            <td>Exception</td>
  80.            <td>
  81.                ${(exception.error)!}
  82.            </td>
  83.        </tr>
  84.        <tr>
  85.            <td>Message</td>
  86.            <td>
  87.                ${(exception.message)!}
  88.            </td>
  89.        </tr>
  90.        </tbody>
  91.    </table>
  92. </div>
  93. </body>
  94. </html>

编写全局异常控制类

  1. /**
  2. * 全局异常控制类
  3. */
  4. @ControllerAdvice
  5. public class GlobalExceptionHandler {
  6.    /**
  7.     * 404异常处理
  8.     */
  9.    @ExceptionHandler(value = NoHandlerFoundException.class)
  10.    @ResponseStatus(HttpStatus.NOT_FOUND)
  11.    public ModelAndView errorHandler(HttpServletRequest request, NoHandlerFoundException exception, HttpServletResponse response) {
  12.        return commonHandler(request, response,
  13.                exception.getClass().getSimpleName(),
  14.                HttpStatus.NOT_FOUND.value(),
  15.                exception.getMessage());
  16.    }
  17.    /**
  18.     * 405异常处理
  19.     */
  20.    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
  21.    public ModelAndView errorHandler(HttpServletRequest request, HttpRequestMethodNotSupportedException exception, HttpServletResponse response) {
  22.        return commonHandler(request, response,
  23.                exception.getClass().getSimpleName(),
  24.                HttpStatus.METHOD_NOT_ALLOWED.value(),
  25.                exception.getMessage());
  26.    }
  27.    /**
  28.     * 415异常处理
  29.     */
  30.    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
  31.    public ModelAndView errorHandler(HttpServletRequest request, HttpMediaTypeNotSupportedException exception, HttpServletResponse response) {
  32.        return commonHandler(request, response,
  33.                exception.getClass().getSimpleName(),
  34.                HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(),
  35.                exception.getMessage());
  36.    }
  37.    /**
  38.     * 500异常处理
  39.     */
  40.    @ExceptionHandler(value = Exception.class)
  41.    public ModelAndView errorHandler (HttpServletRequest request, Exception exception, HttpServletResponse response) {
  42.        return commonHandler(request, response,
  43.                exception.getClass().getSimpleName(),
  44.                HttpStatus.INTERNAL_SERVER_ERROR.value(),
  45.                exception.getMessage());
  46.    }
  47.    /**
  48.     * 业务异常处理
  49.     */
  50.    @ExceptionHandler(value = BasicException.class)
  51.    private ModelAndView errorHandler (HttpServletRequest request, BasicException exception, HttpServletResponse response) {
  52.        return commonHandler(request, response,
  53.                exception.getClass().getSimpleName(),
  54.                exception.getCode(),
  55.                exception.getMessage());
  56.    }
  57.    /**
  58.     * 表单验证异常处理
  59.     */
  60.    @ExceptionHandler(value = BindException.class)
  61.    @ResponseBody
  62.    public ExceptionEntity validExceptionHandler(BindException exception, HttpServletRequest request, HttpServletResponse response) {
  63.        List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
  64.        Map<String,String> errors = new HashMap<>();
  65.        for (FieldError error:fieldErrors) {
  66.            errors.put(error.getField(), error.getDefaultMessage());
  67.        }
  68.        ExceptionEntity entity = new ExceptionEntity();
  69.        entity.setMessage(JSON.toJSONString(errors));
  70.        entity.setPath(request.getRequestURI());
  71.        entity.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
  72.        entity.setError(exception.getClass().getSimpleName());
  73.        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
  74.        return entity;
  75.    }
  76.    /**
  77.     * 异常处理数据处理
  78.     */
  79.    private ModelAndView commonHandler (HttpServletRequest request, HttpServletResponse response,
  80.                                            String error, int httpCode, String message) {
  81.        ExceptionEntity entity = new ExceptionEntity();
  82.        entity.setPath(request.getRequestURI());
  83.        entity.setError(error);
  84.        entity.setCode(httpCode);
  85.        entity.setMessage(message);
  86.        return determineOutput(request, response, entity);
  87.    }
  88.    /**
  89.     * 异常输出处理
  90.     */
  91.    private ModelAndView determineOutput(HttpServletRequest request, HttpServletResponse response, ExceptionEntity entity) {
  92.        if (!(
  93.                request.getHeader("accept").contains("application/json")
  94.                || (request.getHeader("X-Requested-With") != null && request.getHeader("X-Requested-With").contains("XMLHttpRequest"))
  95.        )) {
  96.            ModelAndView modelAndView = new ModelAndView("error");
  97.            modelAndView.addObject("exception", entity);
  98.            return modelAndView;
  99.        } else {
  100.            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
  101.            response.setCharacterEncoding("UTF8");
  102.            response.setHeader("Content-Type", "application/json");
  103.            try {
  104.                response.getWriter().write(ResultJsonTools.build(
  105.                        ResponseCodeConstant.SYSTEM_ERROR,
  106.                        ResponseMessageConstant.APP_EXCEPTION,
  107.                        JSONObject.parseObject(JSON.toJSONString(entity))
  108.                ));
  109.            } catch (IOException e) {
  110.                e.printStackTrace();
  111.            }
  112.            return null;
  113.        }
  114.    }
  115. }

@ControllerAdvice

作用于类上,用于标识该类用于处理全局异常。

@ExceptionHandler

作用于方法上,用于对拦截的异常类型进行处理。value 属性用于指定具体的拦截异常类型,如果有多个 ExceptionHandler 存在,则需要指定不同的 value 类型,由于异常类拥有继承关系,所以 ExceptionHandler 会首先执行在继承树中靠前的异常类型。

BindException

该异常来自于表单验证框架 Hibernate validation,当字段验证未通过时会抛出此异常。

编写测试 Controller

  1. @RestController
  2. public class TestController {
  3.    @RequestMapping(value = "err")
  4.    public void error(){
  5.        throw new BusinessException(400, "业务异常错误信息");
  6.    }
  7.    @RequestMapping(value = "err2")
  8.    public void error2(){
  9.        throw new NullPointerException("手动抛出异常信息");
  10.    }
  11.    @RequestMapping(value = "err3")
  12.    public int error3(){
  13.        int a = 10 / 0;
  14.        return a;
  15.    }
  16. }

使用 AJAX 方式请求时返回的 JSON 格式错误信息。

  1. # /err
  2. {
  3.    "msg": "应用程序异常",
  4.    "code": -1,
  5.    "status_code": 0,
  6.    "data": {
  7.        "path": "/err",
  8.        "code": 400,
  9.        "error": "BusinessException",
  10.        "message": "业务异常错误信息",
  11.        "timestamp": "2018-12-18 11:09:00"
  12.    }
  13. }
  14. # /err2
  15. {
  16.    "msg": "应用程序异常",
  17.    "code": -1,
  18.    "status_code": 0,
  19.    "data": {
  20.        "path": "/err2",
  21.        "code": 500,
  22.        "error": "NullPointerException",
  23.        "message": "手动抛出异常信息",
  24.        "timestamp": "2018-12-18 11:15:15"
  25.    }
  26. }
  27. # /err3
  28. {
  29.    "msg": "应用程序异常",
  30.    "code": -1,
  31.    "status_code": 0,
  32.    "data": {
  33.        "path": "/err3",
  34.        "code": 500,
  35.        "error": "ArithmeticException",
  36.        "message": "/ by zero",
  37.        "timestamp": "2018-12-18 11:15:46"
  38.    }
  39. }
  40. # /err404
  41. {
  42.    "msg": "应用程序异常",
  43.    "code": -1,
  44.    "status_code": 0,
  45.    "data": {
  46.        "path": "/err404",
  47.        "code": 404,
  48.        "error": "NoHandlerFoundException",
  49.        "message": "No handler found for GET /err404",
  50.        "timestamp": "2018-12-18 11:16:11"
  51.    }
  52. }

使用浏览器请求时返回的错误信息界面。

相关文章
|
10月前
|
前端开发 Java UED
"揭秘!如何以戏剧性姿态,利用SpringCloud铸就无懈可击的异常处理铁壁,让你的微服务架构稳如泰山,震撼业界!"
【9月更文挑战第8天】随着微服务架构的普及,Spring Cloud作为一套完整的微服务解决方案被广泛应用。在微服务架构中,服务间调用频繁且复杂,异常处理成为保障系统稳定性和用户体验的关键。传统的异常处理方式导致代码冗余,降低系统可维护性和一致性。因此,基于Spring Cloud封装统一的异常处理机制至关重要。这样不仅可以减少代码冗余、提升一致性,还增强了系统的可维护性,并通过统一的错误响应格式优化了用户体验。具体实现包括定义全局异常处理器、自定义业务异常以及在服务中抛出这些异常。这种方式体现了微服务架构中的“服务治理”和“契约先行”原则,有助于构建健壮、可扩展的系统。
126 2
|
JSON Java 数据格式
Spring Cloud Gateway-自定义异常处理
我们平时在用SpringMVC的时候,只要是经过DispatcherServlet处理的请求,可以通过@ControllerAdvice和@ExceptionHandler自定义不同类型异常的处理逻辑,具体可以参考ResponseEntityExceptionHandler和DefaultHandlerExceptionResolver,底层原理很简单,就是发生异常的时候搜索容器中已经存在的异常处理器并且匹配对应的异常类型,匹配成功之后使用该指定的异常处理器返回结果进行Response的渲染,如果找不到默认的异常处理器则用默认的进行兜底。
826 0
Spring Cloud Gateway-自定义异常处理
|
SpringCloudAlibaba 前端开发 Java
SpringCloud Alibaba微服务实战二十四 - SpringCloud Gateway的全局异常处理
SpringCloud Alibaba微服务实战二十四 - SpringCloud Gateway的全局异常处理
2156 0
|
JSON Java API
基于SpringCloud封装统一的异常处理
在 Spring Cloud 中,可以通过自定义异常处理器来封装统一的异常处理逻辑。异常处理器能够捕获并处理应用程序中的异常,然后返回适当的错误响应。以下是一个基于 Spring Cloud 的统一异常处理的示例
|
安全 JavaScript 小程序
Spring Cloud实战 | 第九篇:Spring Cloud整合Spring Security OAuth2认证服务器统一认证自定义异常处理
Spring Cloud实战 | 第九篇:Spring Cloud整合Spring Security OAuth2认证服务器统一认证自定义异常处理
|
JSON 前端开发 Java
Spring Cloud 如何统一异常处理?写得太好了!
Spring Cloud 如何统一异常处理?写得太好了!
280 0
Spring Cloud 如何统一异常处理?写得太好了!
|
开发框架 Java 微服务
SpringCloud微服务实战——搭建企业级开发框架(七):自定义通用响应消息及统一异常处理
平时开发过程中,无可避免我们需要处理各类异常,所以这里我们在公共模块中自定义统一异常,Spring Boot 提供 @RestControllerAdvice 注解统一异常处理,我们在GitEgg_Platform中新建gitegg-platform-boot子工程,此工程主要用于Spring Boot相关功能的自定义及扩展。 1、修改gitegg-platform-boot的pom.xml,添加spring-boot-starter-web和swagger依赖,设置optional为true,让这个包在项目之间依赖不传递。
353 0
SpringCloud微服务实战——搭建企业级开发框架(七):自定义通用响应消息及统一异常处理
|
监控 Java 微服务
Spring Cloud实战小贴士:Zuul统一异常处理(三)【Dalston版】
Spring Cloud实战小贴士:Zuul统一异常处理(三)【Dalston版】
208 0
Spring Cloud实战小贴士:Zuul统一异常处理(三)【Dalston版】
|
Java API 微服务
Spring Cloud实战小贴士:Zuul统一异常处理(一)
Spring Cloud实战小贴士:Zuul统一异常处理(一)
262 0
|
存储 Java API
Spring Cloud实战小贴士:Zuul统一异常处理(二)
Spring Cloud实战小贴士:Zuul统一异常处理(二)
408 0