一、Java 异常机制概述
Spring Boot 的所有异常处理都基于 java 的。
1.1、Java 异常类图
- Java 内部的异常类 Throwable 包括了 Exception 和 Error 两大类,所有的异常类都是 Object 对象。
- Error 是不可捕捉的异常,通俗的理解就是由于 java 内部 jvm 引起的不可预见的异常,比如 java 虚拟机运行错误,当内存资源错误,将会出现 OutOfMemoryError。此时 java 虚拟机会选择终止线程。
- Excetpion 异常是程序本身引起的,它又分为运行时异常 RuntimeException,和非运行时(编译时)IOException 等异常。
- 运行时异常 RuntimeException 例如:除数为零、 ArrayIndexOutOfBoundException 异常。
- 非运行异常都是可查可捕捉的。Java 编译器会告诉程序他错了,错在哪里,正确的建议什么。我们可以通过 throws 配合 try-catch 来处理,例如:IOException
二、Java异常处理机制
2.1、异常处理机制的分类
在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。
- 抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
- 捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
针对不同的异常类型,Java 对处理的要求不一样
- Error 错误,由于不可捕捉,不可查询,Java 允许不做任何处理。
- 对于运行时异常 RuntimeException 不可查询异常,Java 允许程序忽略运行时异常,Java 系统会自动记录并处理。
- 对于所有可查异常都可捕捉
三、SpringBoot中的异常处理示例
在 Spring Boot 应用程序中,通常统一处理异常的方法有使用注解处理 @ControllerAdvice、@RestControllerAdvice
本示例主要目的处理我们日常 Spring Boot 中的异常处理:
- AOP切面处理异常:在 Web 项目中通过 @ControllerAdvice、@RestControllerAdvice 实现全局异常处理,@ControllerAdvice 和 @RestControllerAdvice 的区别相当于 Controller 和 RestController 的区别。
- 在 Web 项目中实现 404、500 等状态的页面单独渲染
3.1、创建基于 @RestControllerAdvice 全局异常类示例
@RestControllerAdvice 注解是 Spring Boot 用于捕获 @Controller 和 @RestController 层系统抛出的异常( 注意,如果已经编写了 try-catch 且在 catch 模块中没有使用 throw 抛出异常, 则 @RestControllerAdvice 捕获不到异常 )。
@ExceptionHandler 注解用于指定方法处理的 Exception 的类型
控制层 ThrowableController 编写了4个 api 方法,
- /index 是正常的方法;
- /err 是手动抛出异常;
- /match 除数为0的异常 ;
- /nocatch 用了 try-catch 但没有抛出异常,不会被捕捉。
四个 api 其中 /err、/match 会被 CatchExceptionController 捕捉。
3.1.1、ThrowableController
package com.wells.demo.throwable.controller;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Wells on 2019年01月14日
*/
@Api(tags = "ThrowableController", description = "throwable")
@RestController
@RequestMapping("throwable")
public class ThrowableController {
@GetMapping("/index")
public Map<String, Object> index() {
Map<String, Object> map = new HashMap<>();
map.put("status", "0");
map.put("msg", "正常的输出");
return map;
}
/**
* 手动RuntimeException
*/
@GetMapping("/runtimeException")
public Map<Object, Object> err() {
throw new RuntimeException("抛出一个异常");
}
/**
* 这里抛出的是 RuntimeException 不可查异常,虽然没有使用 try-catch 来捕捉 但系统以及帮助我们抛出了一次
* */
@GetMapping("/match")
public Map<String, Object> matcherr() {
Map<String, Object> map = new HashMap<>();
map.put("status", "0");
map.put("msg", "正常的输出");
int j = 0;
Integer i = 9 / j;
return map;
}
/**
* 这里抛出的是 RuntimeException 不可查异 注意这里使用了 try-catch 来捕捉异常,但没有抛出异常
* */
@GetMapping("/nocatch")
public Map<String, Object> nocatch() {
Map<String, Object> map = new HashMap<>();
map.put("status", "0");
map.put("msg", "正常的输出 注意这里使用了 try-catch 来捕捉异常,但没有抛出异常,所以没有异常,因为这里抛出的是 RuntimeException 不可查异常,系统也不会报错。");
int j = 0;
try {
Integer i = 9 / j;
} catch (Exception e) {
}
return map;
}
}
3.1.2、CatchExceptionController
package com.wells.demo.throwable.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import java.util.HashMap;
import java.util.Map;
/**
* Description AOP处理异常: RestControllerAdvice
* Created by wells on 2020-07-14 06:23:34
*/
@RestControllerAdvice(annotations = RestController.class)
public class CatchExceptionController {
private static final Logger logger = LoggerFactory.getLogger(CatchExceptionController.class);
/**
* 处理所有的 RestController 层面的异常
* */
@ExceptionHandler(Exception.class)
@ResponseBody
public final Map<String, Object> handleAllExceptions(Exception ex, WebRequest request) {
logger.error(ex.getMessage());
Map<String, Object> map = new HashMap<>();
map.put("status", -1);
map.put("msg", ex.getLocalizedMessage());
return map;
}
}
3.1.1、Postman测试
http://localhost:8081/throwable/index
{
"msg": "正常的输出",
"status": "0"
}
http://localhost:8081/throwable/err
{
"msg": "抛出一个异常",
"status": -1
}
http://localhost:8081/throwable/match
{
"msg": "/ by zero",
"status": -1
}
http://localhost:8081/throwable/nocatch
{
"msg": "正常的输出 注意这里使用了 try-catch 来捕捉异常,但没有抛出异常",
"status": "0"
}
3.2、创建基于 @ControllerAdvice 全局异常类 并且 返回ModelView 示例
- @ControllerAdvice 和 @RestControllerAdvice 其实就是 @Controller 和 @RestController 的区别。
- 无论是 @ControllerAdvice 还是 @RestControllerAdvice 都是可以捕捉 @Controller 和 @RestController 抛出的异常。
- RestController也可以进行ModelView处理
不同的是,@Controller 异常,我们往往需要更加友好的界面。下面我们使用了 thymeleaf 模板来重新定义 /error 默认路由。
控制层 IndexController 编写了2个方法:
- error.html 在 rerouces/templates/(默认目录)目录下,必须引入 thymeleaf 组件;也可以在application.properties中设置自定义目录;
- /index 是正常的方法;
- /index/err 是人为抛出异常,会被 CatchViewThrowableController 捕捉;
- /index/match 除数为0的异常,会被 CatchViewThrowableController 捕捉;
3.2.1、pom设置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-boot</artifactId>
<groupId>com.wells.demo</groupId>
<version>1.0.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>throwable</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
</project>
3.2.2、application.properties设置
server.port=8081
# 控制彩色中断输出
spring.output.ansi.enabled=ALWAYS
spring.resources.static-locations=classpath:/views/
spring.thymeleaf.prefix=classpath:/views/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.cache=false
3.2.3、ViewThrowableController
package com.wells.demo.throwable.controller;
import io.swagger.annotations.Api;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
/**
* Created by Wells on 2019年01月14日
*/
@Api(tags = "ThrowableController", description = "throwable")
@Controller
@RequestMapping("view/throwable")
public class ViewThrowableController {
@GetMapping("/index")
public String index(Model model) {
model.addAttribute("msg","这是一个index页面的正常消息");
return "index";
}
/**
* 手动RuntimeException
*/
@GetMapping("/runtimeException")
public Map<Object, Object> err() {
throw new RuntimeException("抛出一个异常");
}
/**
* 这里抛出的是 RuntimeException 不可查异常,虽然没有使用 try-catch 来捕捉 但系统以及帮助我们抛出了一次
* 这里抛出异常了,不会最终返回 index,而是走到了全局异常处理中
* */
@GetMapping("/match")
public String matcherr() {
int j = 0;
Integer i = 9 / j;
return "index";
}
}
3.2.4、CatchViewThrowableController
package com.wells.demo.throwable.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
/**
* Description AOP处理异常: ControllerAdvice
* Created by wells on 2020-07-14 06:23:34
*/
@ControllerAdvice(annotations = Controller.class)
public class CatchViewThrowableController {
private static final Logger logger = LoggerFactory.getLogger(CatchViewThrowableController.class);
public static final String DEFAULT_ERROR_VIEW = "error";
/**
* 处理所有的 RestController 层面的异常
* */
@ExceptionHandler(Exception.class)
public final ModelAndView handleAllExceptions(Exception ex, HttpServletRequest request) {
logger.error(ex.getMessage());
ModelAndView modelAndView = new ModelAndView();
// 将异常信息设置如modelAndView
modelAndView.addObject("msg", ex);
modelAndView.addObject("url", request.getRequestURL());
modelAndView.setViewName(DEFAULT_ERROR_VIEW);
// 返回ModelAndView
return modelAndView;
}
}
3.3、根据不同响应码返回不同页面或者处理
略,参考:https://www.cnblogs.com/fishpro/p/spring-boot-study-throwable.html#_label3
代码示例:SpringBoot 异常处理
总结
一般工程中不会用到返回页面的处理,因为前后端已经分离,不会耦合在一起; 主要会用到全局异常处理
- 全局异常处理的思想也是AOP:监听 Controller 注解,然后处理异常,我们也可以封装自己的自定义注解,使用AOP实现;
参考
https://www.cnblogs.com/fishpro/p/spring-boot-study-throwable.html