异常处理用于解决一些程序无法掌控, 但又必须面对的情况。例如,程序需要读取文件、连接网络、使用数据库等,但可能文件不存在、网络不畅通、数据库无效等情况。为了程序能继续运行,此时就需要把这些情况进行异常处理。异常处理的方法通常有以下几种:
捕获异常的格式,见以下代码:
try {
// ...
}catch (Exception e){
// ...
}finally {
// ...
}
一个 try 至少要有一个 calch 语句,或至少要有1个 finally 语句。finally 不是用来处理身也不会捕获异常,是为了做些清理工作,如流的关闭、数据库连接的关闭等。
除用 try 语句处理异常外,还可以用 throw、throws 抛出异常。
执行 throw 语句的地方是一个异常抛出点, 后面必须是一个异常对象, 且必须写在函数中。
throw、throws 的用法见以下代码。
throw {异常对象};
[(修饰符)](返回值类型)(方法名)([参数列表])[throws(异常类)]{......}
在应用程序的开发过程中,经常会自定义异常类,以避免使用 try 产生重复代码。自定义异常类一般是通过扩展 Exception 类来实现的。这样的自定义异常属于检查异常( checked exception )。如果要自定义非检查异常,则需要继承 RuntimeException。
Spring Boot 提供了一个默认处理异常的映射。在 Spring Boot 的 Web 项目中,尝试访问一个不存在的URL,会得到 Spring Boot 中内置的异常处理,如下提示:
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Tue Jul 14 23:11:43 CST 2020
There was an unexpected error (type=Not Found, status=404).
同样的地址,如果发送的清求带有 contentType:"application/json;charset=UTF-8" 则返回的是 JSON 格式的错误结果,见以下输出结果:
{
"timestamp": "2020-07-14T15:30:46.996+00:00",
"status": 404,
"error": "Not Found",
"message": "",
"path": "/select"
}
从上面结果可以看出,Spring Boot 会根据消费者发送的 "Content-Type" 来返回相应的异常内容,如果 "Content-Type" 是 "application/json" ,则返回 JSON 文件;如果 “Content-Type" 是"text/html", 则返回 HTML 文件。
在编写代码时,需要对异常进行处理。进行异常处理的普通的代码是 try...catch 结构。但在开发业务时,只想关注业务正常的代码,对于catch 语句中的捕获异常,希望交给异常捕获来处理,不单独在每个方法中编写。这样不仅可以减少冗余代码,还可以减少因忘记写 catch 而出现错误的概率。
Spring 正好提供了一个非常方便的异常处理方案——控制器通知( @ControllerAdvice 或 @RestControllerAdvice ),它将所有控制器作为一个切面,利用切面技术来实现。
通过基于 @ControllerAdvice 或 @RestControllerAdvice 的注解可以对异常进行全局统一处理,默认对所有的 Contoller 有效。如果要限定生效范围,则可以使用 ControllerAdvice 支持的限定范围方式。
这是 ControllerAdvice 进行统一异常处理的优点,它能够细粒度地控制该异常处理器针对哪些 Controller、包或类型有效。
可以利用这一特性在一个系统实现多个异常处理器,然后 Controller 可以有选择地决定使用哪个,使得异常处理更加灵活、降低侵入性。
异常处理类会包含以下一个或多个方法。
springboot提供了默认的错误映射地址 error
所以头请求写为 @RequestMapping("/error") 或 @RequestMapping("${server.error.path:${error.path:/error}}")
@RestController
/*springboot提供了默认的错误映射地址error
@RequestMapping("${server.error.path:${error.path:/error}}")
@RequestMapping("/error")
上面2种写法都可以
*/
@RequestMapping("/error")
//继承springboot提供的ErrorController
public class TestErrorController implements ErrorController {
@Value("${error.path:/error}")
private String path1; //系统出现错误以后来到error请求进行处理; "/error"
@Value("${server.error.path:${error.path:/error}}")
private String path2; //系统出现错误以后来到error请求进行处理; "/error"
//一定要重写方法,默认返回null就可以,不然报错,因为getErrorPath为空.
@Override
public String getErrorPath() {
return null;
}
//一定要添加url映射,指向error
@RequestMapping
public Map handleError() {
//用Map容器返回信息
Map map = new HashMap();
map.put("code", 404);
map.put("msg", "不存在");
System.out.println(path1); // "/error"
System.out.println(path2); // "/error"
return map;
}
/*这里加一个能正常访问的页面,作为比较
因为写在一个控制器所以它的访问路径是
http://localhost:8080/error/ok*/
@RequestMapping("/ok")
@ResponseBody
public Map noError() {
//用Map容器返回信息
Map map = new HashMap();
map.put("code ", 200);
map.put("msg", "正常,这是测试页面");
return map;
}
}
// 这里不要加 consumes = "text/html;charset=UTF-8" ,否则不成功,部分浏览器提交空值
@RequestMapping(value = "", produces = "text/html;charset=UTF-8")
public String errorHtml() {
//用Map容器返回信息
return "404错误";
}
@RequestMapping(value = "", consumes = "application/json;charset=UTF-8", produces = "application/json;charset=UTF-8")
public Map errorJson() {
//用Map容器返回信息
Map map = new HashMap<>();
map.put("code", 404);
map.put("msg", "不存在");
return map;
}
消费者(浏览器)发送的 Content-Type 是 text/html 时,返回 HTML 格式的 “404错误” 提示:
消费者(浏览器)发送的 Content-Type 是 application/json 时,返回 JSON 格式的错误提示:
自定义异常类需要继承 Exception (异常)类。这里继承 RuntimeException
public class BusinessException extends RuntimeException {
//自定义错误码
private Integer code;
//自定义构造器,必须输入错误码及内容
public BusinessException(int code, String msg) {
super(msg);
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
关于异常,在面试时被提问的概率会比较大,还可能会被问及你知道的异常类有哪些。
RuntimeException 和 Error 是非检查异常,其他的都是检查异常。所有方法都可以在不声明 "throws" 方法的情况下抛出RuntimeException 及其子类,不可以在不声明的情况下抛出非 RuntimeException,即:非 RuntimeException 要自己写 catch 语句处理,如果 RuntimeException 不使用 "try...catch" 进行捕捉,则会导致程序运行中断。
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class CustomerBusinessExceptionHandler {
@ResponseBody
@ExceptionHandler(BusinessException.class)
public Map businessExceptionHandler(BusinessException e) {
Map map = new HashMap();
map.put("code", e.getCode());
map.put("message", e.getMessage());
//发生异常进行日志记录,此处省略
return map;
}
}
创建控制器,以抛出 BusinessException 的自定义异常
@RestController
public class TestController {
@RequestMapping("/BusinessException")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i) {
if (i == 0) {
throw new BusinessException(600, "自定义业务错误");
}
return "success";
}
}