• Spring Cloud 如何统一异常处理?写得太好了!


    作者: BNDong
    链接: https://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]}" ...
    

    Spring Boot 基础就不介绍了,推荐下这个实战教程:

    https://github.com/javastacks/spring-boot-best-practice

    默认异常处理

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

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

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

    自定义异常处理

    引入依赖

    <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 是一个模板引擎,用于我们设置错误输出模板。

    增加配置

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

    Spring Boot 基础就不介绍了,推荐下这个实战教程:

    https://github.com/javastacks/spring-boot-best-practice

    新建错误信息实体

    /**
     * 信息实体
     */
    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{
                 100%;
                max- 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{
                 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>
    

    编写全局异常控制类

    /**
     * 全局异常控制类
     */
    @ControllerAdvice
    public 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

    @RestController
    public 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"
        }
    }
    

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

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

    参考资料:

    近期热文推荐:

    1.1,000+ 道 Java面试题及答案整理(2022最新版)

    2.劲爆!Java 协程要来了。。。

    3.Spring Boot 2.x 教程,太全了!

    4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!

    5.《Java开发手册(嵩山版)》最新发布,速速下载!

    觉得不错,别忘了随手点赞+转发哦!

  • 相关阅读:
    UVa 839 -- Not so Mobile(树的递归输入)
    UVa 548 -- Tree
    UVA 122 -- Trees on the level (二叉树 BFS)
    UVa679 小球下落(树)
    POJ 2255 -- Tree Recovery
    POJ 1451 -- T9
    POJ 2513 -- Colored Sticks
    STL -- heap结构及算法
    nginx利用try_files实现多个源
    nginx location的优先级
  • 原文地址:https://www.cnblogs.com/javastack/p/16403980.html
Copyright © 2020-2023  润新知