一、本课程目标:
- 弄懂为什么springboot需要《全局异常处理器》?
- 编码实战一个springboot《全局异常处理器》
- 封装一个自定义异常 ,并集成进《局异常处理器》
- 把《全局异常处理器》集成进《接口返回值统一标准格式》
二、springboot为什么需要全局异常处理器?
- 先讲下什么是全局异常处理器?
全局异常处理器就是把整个系统的异常统一自动处理,程序员可以做到不用写try...catch - 那为什么需要全局异常呢?
- 第一个原因:不用强制写try-catch,由全局异常处理器统一处理
以下模拟一个Controller的runtime异常
@PostMapping(value="/error1")
public void error1( ){
int i=9/0;
}
int i=9/0 如果不用try-catch捕获的话,客户端就会收到如下异常
{
"timestamp": "2019-09-08T02:54:27.415+0000",
"status": 500,
"error": "Internal Server Error",
"message": "/ by zero",
"path": "/user/error1"
}
返回这种格式,明显不友好,故,我们可以用全局异常处理器来统一处理。
- 第二个原因:自定义异常,只能用全局异常来捕获
例如:如下代码,我们抛出一个RuntimeException,在这种情况下,只能交给全局异常处理器来处理了
@PostMapping(value="/error4")
public void error4( ){
throw new RuntimeException("用户已存在!!");
}
- 第三个原因:JSR303规范的Validator参数校验器,参数校验不通过会抛异常,是无法使用try-catch语句直接捕获,只能使用全局异常处理器了
JSR303规范的Validator参数校验器的异常处理后面课程会单独讲解,本节课暂不讲解。
三、编码实现一个springboot全局异常处理器
步骤1:封装异常内容,统一存在枚举类中
public enum ResultCode {
/* 成功状态码 */
SUCCESS(0, "成功"),
/* 系统500错误*/
SYSTEM_ERROR(10000, "系统异常,请稍后重试"),
/* 参数错误:10001-19999 */
PARAM_IS_INVALID(10001, "参数无效"),
/* 用户错误:20001-29999*/
USER_HAS_EXISTED(20001, "用户名已存在");
private Integer code;
private String message;
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer code() {
return this.code;
}
public String message() {
return this.message;
}
}
步骤2:封装Controller的异常结果response
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ErrorResult {
/**
* 异常状态码
*/
private Integer status;
/**
* 用户看得见的异常,例如 用户名重复!!,
*/
private String message;
/**
* 异常的名字
*/
private String exception;
/**
* 异常堆栈的精简信息
*/
private Object errors;
public static ErrorResult fail(ResultCode resultCode, Throwable e,String message) {
ErrorResult result = ErrorResult.fail(resultCode, e);
result.setMessage(message);
return result;
}
public static ErrorResult fail(ResultCode resultCode, Throwable e) {
ErrorResult result = new ErrorResult();
result.setMessage(resultCode.message());
result.setStatus(resultCode.code());
result.setException(e.getClass().getName());
result.setErrors(e.getStackTrace());
return result;
}
}
步骤3:加个全局异常处理器,对异常进行处理
@RestController
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理运行时异常
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Throwable.class)
public ErrorResult handleThrowable(Throwable e, HttpServletRequest request) {
//TODO 运行是异常,可以在这里记录,用于发异常邮件通知
ErrorResult error =ErrorResult.fail(ResultCode.SYSTEM_ERROR, e);
log.error("URL:{} ,系统异常: ",request.getRequestURI(), e);
return error;
}
}
handleThrowable方法的作用是:捕获运行时异常,并把异常统一封装为ErrorResult对象。
以上有几个细节点我们要单独讲解:
- @ControllerAdvice在《response统一格式封装》课程的时候,我们就讲解过它是增强Controller的扩展功能。而全局异常处理器,就是扩展功能之一。
- @ExceptionHandler统一处理某一类异常,从而能够减少代码重复率和复杂度,@ExceptionHandler(Throwable.class)指处理Throwable的异常。
- @ResponseStatus指定客户端收到的http状态码,这里配置500错误,客户端就显示500错误,
步骤4:体验效果
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping(value="/error1")
public void error1( ){
int i=9/0;
}
}
在浏览器输入 http://127.0.0.1:9090/user/error1 得到以下结果
{
"status": 10000,
"message": "系统异常,请稍后重试",
"exception": "java.lang.ArithmeticException"
}
四、编码实现一个springboot自定义异常
步骤1.封装一个自定义异常
自定义异常通常集成RuntimeException,代码很简单不细讲。
@Data
public class BusinessException extends RuntimeException {
protected Integer code;
protected String message;
public BusinessException(ResultCode resultCode) {
this.code = resultCode.code();
this.message = resultCode.message();
}
}
步骤2.把自定义异常 集成 进全局异常处理器
全局异常处理器只要在上文的基础上,增加一个自定义异常处理方法即可,代码很简单,如下:
@RestController
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/** 处理自定义异常 */
@ExceptionHandler(BusinessException.class)
public ErrorResult handleBusinessException(BusinessException e, HttpServletRequest request) {
ErrorResult error = ErrorResult.builder().status(e.code)
.message(e.message)
.exception(e.getClass().getName())
.build();
log.warn("URL:{} ,业务异常:{}", request.getRequestURI(),error);
return error;
}
/**
* 处理运行时异常
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Throwable.class)
public ErrorResult handleThrowable(Throwable e, HttpServletRequest request) {
//TODO 运行是异常,可以在这里记录,用于发异常邮件通知
ErrorResult error =ErrorResult.fail(ResultCode.SYSTEM_ERROR, e);
log.error("URL:{} ,系统异常: ",request.getRequestURI(), e);
return error;
}
}
步骤3.体验效果
在ResultCode枚举类中加一个枚举,USER_HAS_EXISTED(20001, "用户名已存在")。
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@PostMapping(value="/error3")
public void error3( ){
throw new BusinessException(ResultCode.USER_HAS_EXISTED);
}
}
在浏览器输入 http://127.0.0.1:9090/user/error3 得到以下结果
{
"status": 20001,
"message": "用户名已存在",
"exception": "com.agan.boot.exceptions.BusinessException"
}
五、把“全局异常处理器”集成进“接口返回值统一标准格式”
步骤1.改造ResponseHandler代码
我们要改造《接口返回值统一标准格式》的代码,因为我们在讲解《接口返回值统一标准格式》的时候,就没有处理异常。
@ControllerAdvice(basePackages = "com.agan.boot")
public class ResponseHandler implements ResponseBodyAdvice
以上代码是在《接口返回值统一标准格式》的基础上增加了
if (o instanceof ErrorResult) {
ErrorResult errorResult = (ErrorResult) o;
return Result.fail(errorResult.getStatus(),errorResult.getMessage());
}
其实封装很简单,就是把ErrorResult对象转换Result对象即可。
步骤2.体验效果
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@PostMapping(value="/error3")
public void error3( ){
throw new BusinessException(ResultCode.USER_HAS_EXISTED);
}
}
在浏览器输入 http://127.0.0.1:9090/user/error3 得到以下结果
{
"status": 20001,
"desc": "用户名已存在",
"data": null
}
六:课后练习题
自己手写一个《全局异常处理器》和《接口返回值统一标准格式》
1.模拟一个空指针异常,然后返回以下接口返回值统一标准格式:
{
"code": 10000,
"msg": "系统异常请稍后...",
"data": null
}
2.模拟用户登录,提示以下内容
{
"code": 10000,
"msg": "用户名或密码错误,请重试",
"data": null
}
配套学习资料
- 课后练习作业请提交到QQ群(1号QQ群3000人已满,请加2号群:985378659[群名:SpringBoot架构师])
- 本课程配套免费视频教程
https://study.163.com/course/introduction/1004576013.htm?share=1&shareId=1016481220 - 本课程配套源码地址:https://github.com/agan-java/agan-boot