[转] SpringBoot RESTful 应用中的异常处理小结

[From] https://segmentfault.com/a/1190000006749441

SpringBoot RESTful 应用中的异常处理小结

永顺 2016年08月29日发布

  • 赞  |   6收藏  |  15
  • 8.8k 次浏览

@ControllerAdvice 和 @ExceptionHandler 的区别

  • ExceptionHandler, 方法注解, 作用于 Controller 级别. ExceptionHandler 注解为一个 Controler 定义一个异常处理器.
  • ControllerAdvice, 类注解, 作用于 整个 Spring 工程. ControllerAdvice 注解定义了一个全局的异常处理器.

需要注意的是, ExceptionHandler 的优先级比 ControllerAdvice 高, 即 Controller 抛出的异常如果既可以让 ExceptionHandler 标注的方法处理, 又可以让 ControllerAdvice 标注的类中的方法处理, 则优先让 ExceptionHandler 标注的方法处理.

处理 Controller 中的异常

为了方便地展示 Controller 异常处理的方式, 我创建了一个工程 SpringBootRESTfulErrorHandler, 其源码可以到我的 Github: github.com/yongshun 中找到.
SpringBootRESTfulErrorHandler 工程的目录结构如下:

首先我们定义了三个自定义的异常:
BaseException:

public class BaseException extends Exception {
    public BaseException(String message) {
        super(message);
    }
}

MyException1:

public class MyException1 extends BaseException {
    public MyException1(String message) {
        super(message);
    }
}

MyException2:

public class MyException2 extends BaseException {
    public MyException2(String message) {
        super(message);
    }
}

接着我们在 DemoController 中分别抛出这些异常:

@RestController
public class DemoController {
    private Logger logger = LoggerFactory.getLogger("GlobalExceptionHandler");

    @RequestMapping("/ex1")
    public Object throwBaseException() throws Exception {
        throw new BaseException("This is BaseException.");
    }

    @RequestMapping("/ex2")
    public Object throwMyException1() throws Exception {
        throw new MyException1("This is MyException1.");
    }

    @RequestMapping("/ex3")
    public Object throwMyException2() throws Exception {
        throw new MyException2("This is MyException1.");
    }

    @RequestMapping("/ex4")
    public Object throwIOException() throws Exception {
        throw new IOException("This is IOException.");
    }

    @RequestMapping("/ex5")
    public Object throwNullPointerException() throws Exception {
        throw new NullPointerException("This is NullPointerException.");
    }

    @ExceptionHandler(NullPointerException.class)
    public String controllerExceptionHandler(HttpServletRequest req, Exception e) {
        logger.error("---ControllerException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());
        return e.getMessage();
    }
}
  • /ex1: 抛出 BaseException
  • /ex2: 抛出 MyException1
  • /ex3: 抛出 MyException2
  • /ex4: 抛出 IOException
  • /ex5: 抛出 NullPointerException

当 DemoController 抛出未捕获的异常时, 我们在 GlobalExceptionHandler 中进行捕获并处理:
GlobalExceptionHandler:

@RestController
@ControllerAdvice
public class GlobalExceptionHandler {
    private Logger logger = LoggerFactory.getLogger("GlobalExceptionHandler");

    @ExceptionHandler(value = BaseException.class)
    @ResponseBody
    public Object baseErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        logger.error("---BaseException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());
        return e.getMessage();
    }

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Object defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        logger.error("---DefaultException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());
        return e.getMessage();
    }
}

我们看到, GlobalExceptionHandler 类有两个注解:

  • RestController, 表明 GlobalExceptionHandler 是一个 RESTful Controller, 即它会以 RESTful 的形式返回回复.
  • ControllerAdvice, 表示 GlobalExceptionHandler 是一个全局的异常处理器.

在 GlobalExceptionHandler 中, 我们使用了 ExceptionHandler 注解标注了两个方法:

  • ExceptionHandler(value = BaseException.class): 表示 baseErrorHandler 处理 BaseException 异常和其子异常.
  • ExceptionHandler(value = Exception.class): 表示 defaultErrorHandler 会处理 Exception 异常和其所用子异常.

要注意的是, 和 try...catch 语句块, 异常处理的顺序也是从具体到一般, 即如果 baseErrorHandler 可以处理此异常, 则调用此方法来处理异常, 反之使用 defaultErrorHandler 来处理异常.

既然我们已经实现了 Controller 的异常处理, 那么接下来我们就来测试一下吧.
在浏览器中分别访问这些链接, 结果如下:
/ex1:

/ex2:

/ex3:

/ex4:

/ex5:

可以看到, /ex1, /ex2, /ex3 抛出的异常都由 GlobalExceptionHandler.baseErrorHandler 处理; /ex4 抛出的 IOException 异常由 GlobalExceptionHandler.defaultErrorHandler 处理. 但是 /ex5 抛出的 NullPointerException 异常为什么不是 defaultErrorHandler 处理, 而是由 controllerExceptionHandler 来处理呢? 回想到 @ControllerAdvice 和 @ExceptionHandler 的区别 这以小节中的内容时, 我们就知道原因了: 因为我们在 DemoController 中使用 ExceptionHandler 注解定义了一个 Controller 级的异常处理器, 这个级别的异常处理器的优先级比全局的异常处理器优先级高, 因此 Spring 发现 controllerExceptionHandler 可以处理 NullPointerException 异常时, 就调用这个方法, 而不会调用全局的 defaultErrorHandler 方法了.

处理 404 错误

Spring MVC

SpringBoot 默认提供了一个全局的 handler 来处理所有的 HTTP 错误, 并把它映射为 /error. 当发生一个 HTTP 错误, 例如 404 错误时, SpringBoot 内部的机制会将页面重定向到 /error 中.
例如下图中是一个默认的 SpringBoot 404 异常页面.

这个页面实在是太丑了, 我们能不能自定义一个异常页面呢? 当然可以了, 并且 SpringBoot 也给我们提示了: This application has no explicit mapping for /error, so you are seeing this as a fallback.
因此我们实现一个 /error 映射的 Controller 即可.

public class HttpErrorHandler implements ErrorController {

    private final static String ERROR_PATH = "/error";

    /**
     * Supports the HTML Error View
     *
     * @param request
     * @return
     */
    @RequestMapping(value = ERROR_PATH, produces = "text/html")
    public String errorHtml(HttpServletRequest request) {
        return "404";
    }

    /**
     * Supports other formats like JSON, XML
     *
     * @param request
     * @return
     */
    @RequestMapping(value = ERROR_PATH)
    @ResponseBody
    public Object error(HttpServletRequest request) {
        return "404";
    }

    /**
     * Returns the path of the error page.
     *
     * @return the error path
     */
    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }
}

根据上面代码我们看到, 为了实现自定义的 404 页面, 我们实现了 ErrorController 接口:

public interface ErrorController {
    String getErrorPath();
}

这个接口只有一个方法, 当出现 HTTP 错误时, SpringBoot 会将页面重定向到 getErrorPath 方法返回的页面中. 这样我们就可以实现自定义的错误页面了.

RESTful API

提供一个自定义的 "/error" 页面对 Spring MVC 的服务来说自然是没问题的, 但是如果我们的服务是一个 RESTful 服务的话, 这样做就不行了.
当用户调用了一个不存在的 RESTful API 时, 我们想记录下这个异常访问, 并返回一个代表错误的 JSON 给客户端, 这该怎么实现呢?
我们很自然地想到, 我们可以使用处理异常的那一套来处理 404 错误码.
那么我们来试一下这个想法是否可行吧.

奇怪的是, 当我们在浏览器中随意输入一个路径时, 代码并没有执行到异常处理逻辑中, 而是返回了一个 HTML 页面给我们, 这又是怎么回事呢?
原来 Spring Boot 中, 当用户访问了一个不存在的链接时, Spring 默认会将页面重定向到 **/error** 上, 而不会抛出异常.
既然如此, 那我们就告诉 Spring Boot, 当出现 404 错误时, 抛出一个异常即可. 在 application.properties 中添加两个配置:

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

上面的配置中, 第一个 spring.mvc.throw-exception-if-no-handler-found 告诉 SpringBoot 当出现 404 错误时, 直接抛出异常. 第二个 spring.resources.add-mappings 告诉 SpringBoot 不要为我们工程中的资源文件建立映射. 这两个配置正是 RESTful 服务所需要的.
当加上这两个配置后, 我们再来试一下:

可以看到, 现在确实是在 defaultErrorHandler 中处理了.

本文由 yongshun 发表于个人博客, 采用署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议.
非商业转载请注明作者及出处. 商业转载请联系作者本人
Email: [email protected]
本文标题为: SpringBoot RESTful 应用中的异常处理小结
本文链接为: https://segmentfault.com/a/1190000006749441

时间: 2024-08-28 02:28:22

[转] SpringBoot RESTful 应用中的异常处理小结的相关文章

SpringBoot RESTful 应用中的异常处理小结 [转]

转自 https://segmentfault.com/a/1190000006749441 在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的.不可预知的异常需要处理.每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大. 这里使用 @ControllerAdvice ,@ExceptionHandler(value = BaseException.class) 需要注意的是, Excep

第65课 C++中的异常处理(下)

1. C++中的异常处理 (1)catch语句块可以抛出异常 ①catch中获捕的异常可以被重新抛出 ②抛出的异常需要外层的try-catch块来捕获 ③catch(…)块中抛异常的方法是throw;也是将所有异常重新抛出 (2)catch块中重新抛异常的意义 ①可以被外层try-catch块捕获,并重新解释异常的信息. ②工程开发中使用这样的方式统一异常类型 A.假设我们的私有库使用到了第3方的库函数,如func. B.但其抛出的异常类型为int*类型,很不友好.我们可以在私有库使用func的

Node系列——Node中的异常处理。

1.对异常错误的理解 异常错误应该被分为两种情况:操作失败和程序员失误 1.1.操作失败 这是正确编写的程序在运行时产生的错误.它并不是程序的Bug,反而经常是其它问题. 例如:系统本身(内存不足或者打开文件数过多),系统配置(没有到达远程主机的路由),网络问题(端口挂起),远程服务(500错误,连接失败).具体情况如下: 连接不到服务器 无法解析主机名 无效的用户输入 请求超时 服务器返回500 套接字被挂起 系统内存不足 1.2.程序员失误 这是程序里的Bug.这些错误往往可以在调试阶段通过

SpringBoot系列: Spring支持的异常处理方式

===================================视图函数返回 status code 的方式===================================Spring 有一个专门的枚举类型 HttpStatus, 比如 HttpStatus.NOT_FOUND1. 视图函数返回 ResponseEntity 类型的对象. 2. 在 exception 类加注解 @ResponseStatus, 一旦视图函数抛出这个异常, Spring 就会自动返回设定的 statu

SpringBoot RESTful API 架构风格实践

如果你要问 Spring Boot 做什么最厉害,我想答案就在本章标题 RESTful API 简称 REST API . 1 RESTful API 概述 1.1 什么是 RESTful API Rest 是一种规范,符合 Rest 的 Api 就是 Rest Api.简单的说就是可联网设备利用 HTTP 协议通过 GET.POST.DELETE.PUT.PATCH 来操作具有URI标识的服务器资源,返回统一格式的资源信息,包括 JSON.XML.CSV.ProtoBuf.其他格式. 1.2

SpringBoot Restful

SpringBoot Restful 大家在做Web开发的过程中,method常用的值是get和post. 可事实上,method值还可以是put和delete等等其他值. 既然method值如此丰富,那么就可以考虑使用同一个url,但是约定不同的method来实施不同的业务,这就是Restful的基本考虑. CRUD是最常见的操作,在使用Restful 风格之前,通常的增加做法是这样的: /addCategory?name=xxx 可是使用了Restful风格之后,增加就变成了: /categ

SpringBoot RESTful API返回统一数据格式还不懂?

关于 Spring 的全局处理,有两方面要说: 统一数据返回格式统一异常处理为了将两个问题说明清楚,将分两个章节分别说明,本章主要说第一点有童鞋说,我们项目都做了这种处理,就是在每个 API 都单独工具类将返回值进行封装,但这种不够优雅:我想写最少的代码完成这件事,也许有童鞋说,加几个注解就解决问题了,说的没错,但这篇文章主要是为了说明为什么加了几个注解就解决问题了,目的是希望大家知其所以然. 为了更好的说明问题,本文先说明如何实现,然后再详细剖析实现原理(这很关键) 为什么要做统一数据返回格式

项目中java异常处理

一.java异常类介绍. Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类. 有一篇比较好的blog,http://blog.csdn.net/hguisu/article/details/6155636 介绍java异常. 二.异常处理方式. 在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常. 三.程序中使用. 2种处理方式 1.throw new 异常  在方法体上写throws

数据挖掘中分类算法小结

数据挖掘中分类算法小结 数据仓库,数据库或者其它信息库中隐藏着许多可以为商业.科研等活动的决策提供所需要的知识.分类与预测是两种数据分析形式,它们可以用来抽取能够描述重要数据集合或预测未来数据趋势的模型.分类方法(Classification)用于预测数据对象的离散类别(Categorical Label);预测方法(Prediction )用于预测数据对象的连续取值. 分类技术在很多领域都有应用,例如可以通过客户分类构造一个分类模型来对银行贷款进行风险评估;当前的市场营销中很重要的一个特点是强