SpringBoot RESTful 应用中的异常处理小结
转载:https://segmentfault.com/a/1190000006749441
@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 中处理了.
SpringBoot RESTful 应用中的异常处理小结的更多相关文章
- [转] SpringBoot RESTful 应用中的异常处理小结
[From] https://segmentfault.com/a/1190000006749441 SpringBoot RESTful 应用中的异常处理小结 永顺 2016年08月29日发布 赞 ...
- 第65课 C++中的异常处理(下)
1. C++中的异常处理 (1)catch语句块可以抛出异常 ①catch中获捕的异常可以被重新抛出 ②抛出的异常需要外层的try-catch块来捕获 ③catch(…)块中抛异常的方法是throw; ...
- SpringBoot系列: Spring支持的异常处理方式
===================================视图函数返回 status code 的方式===================================Spring 有 ...
- C#不用union,而是有更好的方式实现 .net自定义错误页面实现 .net自定义错误页面实现升级篇 .net捕捉全局未处理异常的3种方式 一款很不错的FLASH时种插件 关于c#中委托使用小结 WEB网站常见受攻击方式及解决办法 判断URL是否存在 提升高并发量服务器性能解决思路
C#不用union,而是有更好的方式实现 用过C/C++的人都知道有个union,特别好用,似乎char数组到short,int,float等的转换无所不能,也确实是能,并且用起来十分方便.那C# ...
- SpringBoot小技巧:统一异常处理
SpringBoot小技巧:统一异常处理 情景描述 对于接口的定义,我们通常会有一个固定的格式,比如: 但是调用方在请求我们的API时把接口地址写错了,就会得到一个404错误,且不同于我们定义的数据格 ...
- C中的异常处理
1,C 语言崇尚简洁高效,因此语言本身并没有异常处理的相关语法规则,但是异常处理在 C 语言中 是存在的,我们有必要从 C 语言开始先看一看 C 语言中的异常处理是怎样, 然后对比 C++ 里面的异常 ...
- SpringBoot RESTful API 架构风格实践
如果你要问 Spring Boot 做什么最厉害,我想答案就在本章标题 RESTful API 简称 REST API . 本项目源码下载 1 RESTful API 概述 1.1 什么是 RESTf ...
- SpringBoot第七集:异常处理与整合JSR303校验(2020最新最易懂)
SpringBoot第七集:异常处理与整合JSR303校验(2020最新最易懂) 一.SpringBoot全局异常 先讲下什么是全局异常处理器? 全局异常处理器就是把整个系统的异常统一自动处理,程序员 ...
- SpringBoot Restful
SpringBoot Restful 大家在做Web开发的过程中,method常用的值是get和post. 可事实上,method值还可以是put和delete等等其他值. 既然method值如此丰富 ...
随机推荐
- hdu 1358 Period
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1358 思路:Next数组的用法,在第i个位置上如果有i%(i-Next[i])==0的话最小循环节就是 ...
- T-SQL函数总结
T-SQL函数的类别和描述. 函数类别 作用 聚合函数 执行的操作是将多个值合并为一个值.例如COUNT.SUM.MIN 和 MAX. 配置函数 是一种标量函数,可返回有关配置设置的信息. 转换函数 ...
- HTML行为元素和块级元素及语义化
块级元素 div - dl - form 交互表单h1 - h6 标题 hr 水平分割线p 段落ul 非排序列表table 表格 行内元素 a 链接br 换行em 强调i 斜体img 图片input ...
- SqlServer性能检测和优化工具使用详细(转)
转载链接:http://www.cnblogs.com/knowledgesea/p/3683505.html 工具概要 如果你的数据库应用系统中,存在有大量表,视图,索引,触发器,函数,存储过程,s ...
- mysql explain用法
explain显示了mysql如何使用索引来处理select语句以及连接表.可以帮助选择更好的索引和写出更优化的查询语句. 使用方法,在select语句前加上explain就可以了,如: explai ...
- iOS NSOperation 异步加载图片 封装NSOperation 代理更新
#import <Foundation/Foundation.h> @class MYOperation; @protocol MYOperationDelecate <NSObje ...
- HDU 3642 Get The Treasury (线段树扫描线)
题意:给你一些长方体,问你覆盖三次及以上的体积有多大 首先我们观察x轴y轴一样很大,但是z轴很小,所以我们可以枚举z轴(-500,500),注意我们枚举的是每一段长度为一的z轴的xy轴的面积而不是点. ...
- 数据类型和Json格式
1. 前几天,我才知道有一种简化的数据交换格式,叫做yaml. 我翻了一遍它的文档,看懂的地方不多,但是有一句话令我茅塞顿开. 它说,从结构上看,所有的数据(data)最终都可以分解成三种类型: 第一 ...
- 浅谈SQLiteOpenHelper之onCreate例子
哈喽大家好!如果你感觉SQLiteOpenHelper不懂的可以看一下. onCreate(SQLiteDatabase db) : 当数据库被首次创建时执行该方法,一般将创建表等初始化操作在该方法 ...
- Android 自动化测试—robotium(十一) robotium实现微博绑定
利用robotium实现微博的自动绑定 android应用进行微博绑定主要有两种方式,一种客户端实现个webView,请求微博默认的授权页,另一种则是直接调用微博客户端,这里实现的为前一种,即进和微博 ...