​Spring Cloud:统一异常处理

沙海
沙海
沙海
718
文章
2
评论
2021年2月26日11:27:25
评论
7 12340字阅读41分8秒
摘要

速读摘要

速读摘要

会根据请求方式分别跳转到以JSON格式或以界面显示的/error地址中显示错误信息。BasicException继承了RuntimeException,并在原有的Message基础上增加了错误码code的内容。BusinessException则是在业务中具体使用的自定义异常类,起到了对不同的异常信息进行分类的作用。作用于方法上,用于对拦截的异常类型进行处理。value类型,由于异常类拥有继承关系,所以ExceptionHandler会首先执行在继承树中靠前的异常类型。

原文约 2879 | 图片 8 | 建议阅读 6 分钟 | 评价反馈

​Spring Cloud:统一异常处理

dbnuo 程序员闪充宝

​Spring Cloud:统一异常处理

作者:BNDong

www.cnblogs.com/bndong/p/10135370.html

在启动应用时会发现在控制台打印的日志中出现了两个路径为 {[/error]} 的访问地址,当系统中发送异常错误时,Spring Boot 会根据请求方式分别跳转到以 JSON 格式或以界面显示的 /error 地址中显示错误信息。

2018-12-18 09:36:24.627  INFO 19040 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" ...2018-12-18 09:36:24.632  INFO 19040 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" ...

默认异常处理

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

{    "timestamp": "2018-12-18T01:50:51.196+0000",    "status": 404,    "error": "Not Found",    "message": "No handler found for GET /err404",    "path": "/err404"}

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

​Spring Cloud:统一异常处理

自定义异常处理

引入依赖

<dependency>    <groupId>com.alibaba</groupId>    <artifactId>fastjson</artifactId>    <version>1.2.54</version></dependency><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-freemarker</artifactId></dependency>

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

增加配置

properties

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

yml

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

新建错误信息实体

/** * 信息实体 */public class ExceptionEntity implements Serializable {    private static final long serialVersionUID = 1L;    private String message;    private int    code;    private String error;    private String path;    @JSONField(format = "yyyy-MM-dd hh:mm:ss")    private Date timestamp = new Date();    public static long getSerialVersionUID() {        return serialVersionUID;    }    public String getMessage() {        return message;    }    public void setMessage(String message) {        this.message = message;    }    public int getCode() {        return code;    }    public void setCode(int code) {        this.code = code;    }    public String getError() {        return error;    }    public void setError(String error) {        this.error = error;    }    public String getPath() {        return path;    }    public void setPath(String path) {        this.path = path;    }    public Date getTimestamp() {        return timestamp;    }    public void setTimestamp(Date timestamp) {        this.timestamp = timestamp;    }}

新建自定义异常

/** * 自定义异常 */public class BasicException extends RuntimeException {    private static final long serialVersionUID = 1L;    private int code = 0;    public BasicException(int code, String message) {        super(message);        this.code = code;    }    public int getCode() {        return this.code;    }}
/** * 业务异常 */public class BusinessException extends BasicException {    private static final long serialVersionUID = 1L;    public BusinessException(int code, String message) {        super(code, message);    }}

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

新建 error.ftl 模板文件

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

<!DOCTYPE html><html><head>    <meta name="robots" content="noindex,nofollow" />    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">    <style>        h2{            color: #4288ce;            font-weight: 400;            padding: 6px 0;            margin: 6px 0 0;            font-size: 18px;            border-bottom: 1px solid #eee;        }        /* Exception Variables */        .exception-var table{            width: 100%;            max-width: 500px;            margin: 12px 0;            box-sizing: border-box;            table-layout:fixed;            word-wrap:break-word;        }        .exception-var table caption{            text-align: left;            font-size: 16px;            font-weight: bold;            padding: 6px 0;        }        .exception-var table caption small{            font-weight: 300;            display: inline-block;            margin-left: 10px;            color: #ccc;        }        .exception-var table tbody{            font-size: 13px;            font-family: Consolas,"Liberation Mono",Courier,"微软雅黑";        }        .exception-var table td{            padding: 0 6px;            vertical-align: top;            word-break: break-all;        }        .exception-var table td:first-child{            width: 28%;            font-weight: bold;            white-space: nowrap;        }        .exception-var table td pre{            margin: 0;        }    </style></head><body><div class="exception-var">    <h2>Exception Datas</h2>    <table>        <tbody>        <tr>            <td>Code</td>            <td>                ${(exception.code)!}            </td>        </tr>        <tr>            <td>Time</td>            <td>                ${(exception.timestamp?datetime)!}            </td>        </tr>        <tr>            <td>Path</td>            <td>                ${(exception.path)!}            </td>        </tr>        <tr>            <td>Exception</td>            <td>                ${(exception.error)!}            </td>        </tr>        <tr>            <td>Message</td>            <td>                ${(exception.message)!}            </td>        </tr>        </tbody>    </table></div></body></html>

编写全局异常控制类

/** * 全局异常控制类 */@ControllerAdvicepublic class GlobalExceptionHandler {    /**     * 404异常处理     */    @ExceptionHandler(value = NoHandlerFoundException.class)    @ResponseStatus(HttpStatus.NOT_FOUND)    public ModelAndView errorHandler(HttpServletRequest request, NoHandlerFoundException exception, HttpServletResponse response) {        return commonHandler(request, response,                exception.getClass().getSimpleName(),                HttpStatus.NOT_FOUND.value(),                exception.getMessage());    }    /**     * 405异常处理     */    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)    public ModelAndView errorHandler(HttpServletRequest request, HttpRequestMethodNotSupportedException exception, HttpServletResponse response) {        return commonHandler(request, response,                exception.getClass().getSimpleName(),                HttpStatus.METHOD_NOT_ALLOWED.value(),                exception.getMessage());    }    /**     * 415异常处理     */    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)    public ModelAndView errorHandler(HttpServletRequest request, HttpMediaTypeNotSupportedException exception, HttpServletResponse response) {        return commonHandler(request, response,                exception.getClass().getSimpleName(),                HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(),                exception.getMessage());    }    /**     * 500异常处理     */    @ExceptionHandler(value = Exception.class)    public ModelAndView errorHandler (HttpServletRequest request, Exception exception, HttpServletResponse response) {        return commonHandler(request, response,                exception.getClass().getSimpleName(),                HttpStatus.INTERNAL_SERVER_ERROR.value(),                exception.getMessage());    }    /**     * 业务异常处理     */    @ExceptionHandler(value = BasicException.class)    private ModelAndView errorHandler (HttpServletRequest request, BasicException exception, HttpServletResponse response) {        return commonHandler(request, response,                exception.getClass().getSimpleName(),                exception.getCode(),                exception.getMessage());    }    /**     * 表单验证异常处理     */    @ExceptionHandler(value = BindException.class)    @ResponseBody    public ExceptionEntity validExceptionHandler(BindException exception, HttpServletRequest request, HttpServletResponse response) {        List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();        Map<String,String> errors = new HashMap<>();        for (FieldError error:fieldErrors) {            errors.put(error.getField(), error.getDefaultMessage());        }        ExceptionEntity entity = new ExceptionEntity();        entity.setMessage(JSON.toJSONString(errors));        entity.setPath(request.getRequestURI());        entity.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());        entity.setError(exception.getClass().getSimpleName());        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());        return entity;    }    /**     * 异常处理数据处理     */    private ModelAndView commonHandler (HttpServletRequest request, HttpServletResponse response,                                            String error, int httpCode, String message) {        ExceptionEntity entity = new ExceptionEntity();        entity.setPath(request.getRequestURI());        entity.setError(error);        entity.setCode(httpCode);        entity.setMessage(message);        return determineOutput(request, response, entity);    }    /**     * 异常输出处理     */    private ModelAndView determineOutput(HttpServletRequest request, HttpServletResponse response, ExceptionEntity entity) {        if (!(                request.getHeader("accept").contains("application/json")                || (request.getHeader("X-Requested-With") != null && request.getHeader("X-Requested-With").contains("XMLHttpRequest"))        )) {            ModelAndView modelAndView = new ModelAndView("error");            modelAndView.addObject("exception", entity);            return modelAndView;        } else {            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());            response.setCharacterEncoding("UTF8");            response.setHeader("Content-Type", "application/json");            try {                response.getWriter().write(ResultJsonTools.build(                        ResponseCodeConstant.SYSTEM_ERROR,                        ResponseMessageConstant.APP_EXCEPTION,                        JSONObject.parseObject(JSON.toJSONString(entity))                ));            } catch (IOException e) {                e.printStackTrace();            }            return null;        }    }}

@ControllerAdvice

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

@ExceptionHandler

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

BindException

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

编写测试 Controller

@RestControllerpublic class TestController {    @RequestMapping(value = "err")    public void error(){        throw new BusinessException(400, "业务异常错误信息");    }    @RequestMapping(value = "err2")    public void error2(){        throw new NullPointerException("手动抛出异常信息");    }    @RequestMapping(value = "err3")    public int error3(){        int a = 10 / 0;        return a;    }}

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

# /err{    "msg": "应用程序异常",    "code": -1,    "status_code": 0,    "data": {        "path": "/err",        "code": 400,        "error": "BusinessException",        "message": "业务异常错误信息",        "timestamp": "2018-12-18 11:09:00"    }}# /err2{    "msg": "应用程序异常",    "code": -1,    "status_code": 0,    "data": {        "path": "/err2",        "code": 500,        "error": "NullPointerException",        "message": "手动抛出异常信息",        "timestamp": "2018-12-18 11:15:15"    }}# /err3{    "msg": "应用程序异常",    "code": -1,    "status_code": 0,    "data": {        "path": "/err3",        "code": 500,        "error": "ArithmeticException",        "message": "/ by zero",        "timestamp": "2018-12-18 11:15:46"    }}# /err404{    "msg": "应用程序异常",    "code": -1,    "status_code": 0,    "data": {        "path": "/err404",        "code": 404,        "error": "NoHandlerFoundException",        "message": "No handler found for GET /err404",        "timestamp": "2018-12-18 11:16:11"    }}

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

​Spring Cloud:统一异常处理

​Spring Cloud:统一异常处理

​Spring Cloud:统一异常处理

​Spring Cloud:统一异常处理

示例代码:https://github.com/BNDong/spring-cloud-examples/tree/master/spring-cloud-zuul/cloud-zuul

参考资料

《微服务 分布式架构开发实战》 龚鹏 著

https://www.jianshu.com/p/1a49fa436623

MySQL用得好好的,为什么要转ES?Spring 中的 Controller 和Service是线程安全的吗?JAVA实现PDF和EXCEL生成和数据动态插入以及导出SpringBoot+ Dubbo + Mybatis + Nacos +Seata整合来实现Dubbo分布式事务一个 SpringBoot 项目该包含哪些?发现一款牛逼的IDEA插件:检测代码漏洞,一键修复!springboot+redis+Interceptor+自定义annotation实现接口自动幂等100W个微信红包封面,速度领取!!!API接口的安全设计验证:ticket,签名,时间戳点击阅读全文前往微服务电商教程

阅读原文

继续阅读
历史上的今天
2月
26
weinxin
资源分享QQ群
本站是一个IT技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
  • 版权声明:本站是Java秀团队的技术分享社区,我们会经常分享资源和教程。
  • 转载请注明:​Spring Cloud:统一异常处理 - Java秀 ☜(ˆ▽ˆ)
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: