Spring Boot异常处理
1. 前言
SpringBoot的项目已经对有一定的异常处理了,但是对于我们开发者而言可能就不太合适了,因此我们需要对这些异常进行统一的捕获并处理。SpringBoot中有一个ControllerAdvice的注解,使用该注解表示开启了全局异常的捕获,我们只需在自定义一个方法使用ExceptionHandler注解然后定义捕获异常的类型即可对这些捕获的异常进行统一的处理。
2. Spring Boot 默认异常处理机制
Spring Boot 开发的 Web 项目具备默认的异常处理机制,无须编写异常处理相关代码,即可提供默认异常机制,下面具体演示下。
2.1 使用 Spring Initializr 创建项目
Spring Boot 版本选择 2.2.5 ,Group 为 com.imooc , Artifact 为 spring-boot-exception-default ,生成项目后导入 Eclipse 开发环境。
2.2 引入项目依赖
引入 Web 项目依赖即可。
实例:
<!-- web项目依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
2.3 Spring Boot 默认异常处理
我们在启动项目, Spring Boot Web 项目默认启动端口为 8080 ,所以直接访问 http://127.0.0.1:8080
,显示如下:
如上图所示,Spring Boot 默认的异常处理机制生效,当出现异常时会自动转向 /error
路径。
3. 控制器返回视图时的异常处理
在使用模板引擎开发 Spring Boot Web 项目时,控制器会返回视图页面。我们使用 Thymeleaf 演示控制器返回视图时的异常处理方式,其他模板引擎处理方式也是相似的。
3.1 使用 Spring Initializr 创建项目
Spring Boot 版本选择 2.2.5 ,Group 为 com.imooc , Artifact 为 spring-boot-exception-controller,生成项目后导入 Eclipse 开发环境。
3.2 引入项目依赖
引入 Web 项目依赖、热部署依赖。此处使用 Thymeleaf 演示控制器返回视图时的异常处理方式,所以引入 Thymeleaf 依赖。
实例:
<!-- web项目依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 热部署 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <!-- ThymeLeaf依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
3.3 定义异常类
在异常处理之前,我们应该根据业务场景具体情况,定义一系列的异常类,习惯性的还会为各种异常分配错误码,如下图为支付宝开放平台的公共错误码信息。
本节我们为了演示,简单的定义 2 个异常类,包含错误码及错误提示信息。
实例:
/** * 自定义异常 */public class BaseException extends Exception { /** * 错误码 */ private int code; /** * 错误提示信息 */ private String msg; public BaseException(int code, String msg) { super(); this.code = code; this.msg = msg; } // 省略get set}
实例:
/** * 密码错误异常 */public class PasswordException extends BaseException { public PasswordException() { super(10001, "密码错误"); }}
实例:
/** * 验证码错误异常 */public class VerificationCodeException extends BaseException { public VerificationCodeException() { super(10002, "验证码错误"); }}
3.4 控制器抛出异常
定义控制器 GoodsController ,然后使用注解 @Controller 标注该类,类中方法的返回值即为视图文件名。
在 GoodsController 类定义 4 个方法,分别用于正常访问、抛出密码错误异常、抛出验证码错误异常、抛出未自定义的异常,代码如下。
实例:
/** * 商品控制器 */@Controllerpublic class GoodsController { /** * 正常方法 */ @RequestMapping("/goods") public String goods() { return "goods";// 跳转到resource/templates/goods.html页面 } /** * 抛出密码错误异常的方法 */ @RequestMapping("/checkPassword") public String checkPassword() throws PasswordException { if (true) { throw new PasswordException();// 模拟抛出异常,便于测试 } return "goods"; } /** * 抛出验证码错误异常的方法 */ @RequestMapping("/checkVerification") public String checkVerification() throws VerificationCodeException { if (true) { throw new VerificationCodeException();// 模拟抛出异常,便于测试 } return "goods"; } /** * 抛出未自定义的异常 */ @RequestMapping("/other") public String other() throws Exception { int a = 1 / 0;// 模拟异常 return "goods"; }}
3.5 开发基于 @ControllerAdvice 的全局异常类
@ControllerAdvice 注解标注的类可以处理 @Controller 标注的控制器类抛出的异常,然后进行统一处理。
实例:
/** * 控制器异常处理类 */@ControllerAdvice(annotations = Controller.class) // 全局异常处理 public class ControllerExceptionHandler { @ExceptionHandler({ BaseException.class }) // 当发生BaseException类(及其子类)的异常时,进入该方法 public ModelAndView baseExceptionHandler(BaseException e) { ModelAndView mv = new ModelAndView(); mv.addObject("code", e.getCode()); mv.addObject("message", e.getMessage()); mv.setViewName("myerror");// 跳转到resource/templates/myerror.html页面 return mv; } @ExceptionHandler({ Exception.class }) // 当发生Exception类的异常时,进入该方法 public ModelAndView exceptionHandler(Exception e) { ModelAndView mv = new ModelAndView(); mv.addObject("code", 99999);// 其他异常统一编码为99999 mv.addObject("message", e.getMessage()); mv.setViewName("myerror");// 跳转到resource/templates/myerror.html页面 return mv; }}
按照 ControllerExceptionHandler 类的处理逻辑,当发生 BaseException 类型的异常时,会跳转到 myerror.html 页面,并显示相应的错误码和错误信息;当发生其他类型的异常时,错误码为 99999 ,错误信息为相关的异常信息。
3.6 开发前端页面
在 resource/templates 下分别新建 goods.html 和 myerror.html 页面,作为正常访问及发生异常时跳转的视图页面。
实例:
<!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8"> <title>goods.html页面</title> </head> <body> <div>商品信息页面</div> </body> </html>
实例:
<!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8"> <title>myerror.html页面</title> </head> <body> 错误码: <span th:text="${code}"></span> 错误信息: <span th:text="${message}"></span> </body> </html>
3.7 测试
启动项目,分别访问控制器中的 4 个方法,结果如下:
可见,当控制器方法抛出异常时,会按照全局异常类设定的逻辑统一处理。
4. 控制器返回 JSON 数据时的异常处理
在控制器类上添加 @RestController 注解,控制器方法处理完毕后会返回 JSON 格式的数据。
此时,可以使用 @RestControllerAdvice 注解标注的类 ,来捕获 @RestController 标注的控制器抛出的异常。
4.1 使用 Spring Initializr 创建项目
Spring Boot 版本选择 2.2.5 ,Group 为 com.imooc , Artifact 为 spring-boot-exception-restcontroller,生成项目后导入 Eclipse 开发环境。
4.2 引入项目依赖
引入 Web 项目依赖、热部署依赖即可。
实例:
<!-- web项目依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 热部署 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency>
4.3 定义异常类
还是使用上文中定义的异常类即可。
4.4 统一控制器返回数据格式
这时候,我们就需要思考一个问题了。前端请求后端控制器接口后,怎么区分后端接口是正常返回结果,还是发生了异常?
不论后端接口是正常执行,还是中间发生了异常,最好给前端返回统一的数据格式,便于前端统一分析处理。
OK,此时我们就可以封装后端接口返回的业务逻辑对象 ResultBo ,代码如下:
实例:
/** * 后端接口返回的统一业务逻辑对象 */public class ResultBo<T> { /** * 错误码 0表示没有错误(异常) 其他数字代表具体错误码 */ private int code; /** * 后端返回消息 */ private String msg; /** * 后端返回的数据 */ private T data; /** * 无参数构造函数 */ public ResultBo() { this.code = 0; this.msg = "操作成功"; } /** * 带数据data构造函数 */ public ResultBo(T data) { this(); this.data = data; } /** * 存在异常的构造函数 */ public ResultBo(Exception ex) { if (ex instanceof BaseException) { this.code = ((BaseException) ex).getCode(); this.msg = ex.getMessage(); } else { this.code = 99999;// 其他未定义异常 this.msg = ex.getMessage(); } } // 省略 get set}
4.5 控制器抛出异常
定义控制器 RestGoodsController ,并使用 @RestController 注解标注。在其中定义 4 个方法,然后分别用于正常访问、抛出密码错误异常、抛出验证码错误异常,以及抛出不属于自定义异常类的异常。
实例:
/** * Rest商品控制器 */@RestControllerpublic class RestGoodsController { /** * 正常方法 */ @RequestMapping("/goods") public ResultBo goods() { return new ResultBo<>(new ArrayList());// 正常情况下应该返回商品列表 } /** * 抛出密码错误异常的方法 */ @RequestMapping("/checkPassword") public ResultBo checkPassword() throws PasswordException { if (true) { throw new PasswordException();// 模拟抛出异常,便于测试 } return new ResultBo<>(true);// 正常情况下应该返回检查密码的结果true或false } /** * 抛出验证码错误异常的方法 */ @RequestMapping("/checkVerification") public ResultBo checkVerification() throws VerificationCodeException { if (true) { throw new VerificationCodeException();// 模拟抛出异常,便于测试 } return new ResultBo<>(true);// 正常情况下应该返回检查验证码的结果true或false } /** * 抛出未自定义的异常 */ @RequestMapping("/other") public ResultBo other() throws Exception { int a = 1 / 0;// 模拟异常 return new ResultBo<>(true); }}
4.6 开发基于 @RestControllerAdvice 的全局异常类
@RestControllerAdvice 注解标注的类可以处理 RestController 控制器类抛出的异常,然后进行统一处理。
实例:
/** * Rest控制器异常处理类 */@RestControllerAdvice(annotations = RestController.class) // 全局异常处理public class RestControllerExceptionHandler { /** * 处理BaseException类(及其子类)的异常 */ @ExceptionHandler({ BaseException.class }) public ResultBo baseExceptionHandler(BaseException e) { return new ResultBo(e); } /** * 处理Exception类的异常 */ @ExceptionHandler({ Exception.class }) public ResultBo exceptionHandler(Exception e) { return new ResultBo(e); }}
4.7 测试
启动项目,分别尝试访问控制器中的 4 个接口,结果如下。
5. 小结
Spring Boot 的默认异常处理机制,实际上只能做到提醒开发者 “这个后端接口不存在” 的作用,作用非常有限。
所以我们在开发 Spring Boot 项目时,需要根据项目的实际情况,定义各类异常,并站在全局的角度统一处理异常。
不管项目有多少层次,所有异常都可以向外抛出,直到控制器层进行集中处理。
对于返回视图的控制器,如果没发生异常就跳转正常页面,如果发生异常可以自定义错误信息页面。
对于返回 JSON 数据的控制器,最好是定义统一的数据返回格式,便于前端根据返回信息进行正常或者异常情况的处理。
原文链接: https://www.yukx.com/spring/article/details/2145.html 优科学习网Spring Boot异常处理
-
在HTML中,如果你想让一个输入框(input元素)不可编辑,你可以通过设置其readonly属性来实现。示例如下:input type="text" value="此处内容不可编辑" readonly在上述代码中,readonly属性使得用户无法修改输入框中的内容。另外,如果你希望输入框完全不可交
-
ASP.NET教程ASP.NET又称为ASP+,基于.NETFramework的Web开发平台,是微软公司推出的新一代脚本语言。ASP.NET是一个使用HTML、CSS、JavaScript和服务器脚本创建网页和网站的开发框架。ASP.NET支持三种不一样的开发模式:WebPages(Web页面)、
-
C# 判断判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。下面是大多数编程语言中典型的判断结构的通常形式:判断语句C#提供了以下类型的判断语句。点击链接查看每个语句的细节。语句描述if语句一个 if语句 由一个布尔表达式后跟
-
C#循环有的时候,可能需要多次执行同一块代码。通常情况下,语句是顺序执行的:函数中的第一个语句先执行,接着是第二个语句,依此类推。编程语言提供了允许更为复杂的执行路径的多种控制结构。循环语句允许我们多次执行一个语句或语句组,下面是大多数编程语言中循环语句的通常形式:循环类型C#提供了以下几种循环类型
-
C#数组(Array)数组是一个存储相同类型元素的固定大小的顺序集合。数组是用来存储数据的集合,一般认为数组是一个同一类型变量的集合。声明数组变量并不是声明number0、number1、...、number99一个个单独的变量,而是声明一个就像numbers这样的变量,然后使用numbers[0]
-
ASP.NET是一个由微软公司开发的用于构建Web应用程序的框架,它是.NETFramework的一部分。它提供了一种模型-视图-控制器(MVC)架构、Web表单以及最新的ASP.NETCore中的RazorPages等多种开发模式,可以用来创建动态网页和Web服务。以下是一些基础的ASP.NET编