Spring Boot 快速入门系列(VII)—— 全局异常处理篇

点击上方蓝色字体关注我吧

    一起学习,一起进步,做积极的人!

前言

《Spring Boot 快速入门系列》上一节「接口规范篇」讲完了,小伙伴们是否已经掌握了基本的接口编写规范(后面会有一篇专门演示在线接口文档内容)。

Spring Boot 快速入门系列:

Spring Boot 快速入门系列(先导篇) —— 从 Hello World 开始

Spring Boot 快速入门系列(I) —— 属性配置篇

Spring Boot 快速入门系列(II)—— 数据操作篇之 Spring Data JPA

Spring Boot 快速入门系列(III)—— 数据操作篇之 JdbcTemplate
Spring Boot 快速入门系列(IV)—— 数据操作篇之 MyBatis

Spring Boot 快速入门系列(V)—— 事务管理篇之 @Transactional

Spring Boot 快速入门系列(VI)—— 接口规范篇

对于一个接口服务来说,异常处理显得尤为重要,因为没有任何程序是没有 bug 的。那么在程序可能出现异常的地方,需要我们手动捕获和处理异常。比如像下面这 3 种方式见的很多:

try {    
    // 可能发生异常的逻辑    
    ...    
    doSomething(); 
} catch (ExceptionType e) {    
    // 异常处理    
    logger.error(e);    
    throw new MyException(e.getMessage());
}
// or
try {    
    // 可能发生异常的逻辑    
    ...    
    doSomething(); 
} catch (ExceptionType1 e1) {    
    // 异常处理    
    logger.error(e1);    
    throw new MyException(e1.getMessage());
} catch (ExceptionType2 e2) {    
    // 异常处理    
    logger.error(e2);    
    throw new MyException(e2.getMessage());
}
// or
try {    
    // 可能发生异常的逻辑    
    ...    
    doSomething(); 
} catch (ExceptionType1 e1 | ExceptionType2 e2) {    
    // 异常处理    
    logger.error(e1 + "\n" + e2);    
    throw new MyException(e1.getMessage() + e2.getMessage());
}

像这种标准的 try { ... } catch(ExceptionTypes e) { ... } 是处理异常的常见解决问题的方式,但是实际程序我们不可能每一段代码都会 try-catch 处理,还有一些异常可能无法提前预知,没有显式的捕获处理,那如果此时程序发生异常,我们该如何处理呢。

那么今天我们就来讲讲对于基于 Spring Boot 构建的项目中如何处理这些异常问题的。

异常处理

1. @ExceptionHandler 注解

了解 SpringMVC 或 Spring Boot 框架的人应该知道 @ExceptionHandler 这个异常处理注解,我们可以使用这个注解来捕获程序异常。

1.1 单一 Action/Controller 异常处理

这种使用 @ExceptionHandler 注解的场景较少,但作为学习异常处理还是非常不错的,只需在对应的 xxxAction / xxxController 控制层增加一个异常处理的方法,并使用 @ExceptionHandler 注解标识即可。

@ExceptionHandler(Exception.class)
public Result handleException() {    
    // 统一返回数据格式:《Spring Boot 快速入门之参数规范篇》的示例代码  
    // 以下相同  
    return new ResponseDataVO(ResponseStatusCode.CUSTOMEXCEPTION);
}

1.2 基类 Action/Controller 异常处理

在应用广泛的 SSH 或是 SSM 框架项目中,一般存在着多个 xxxAction / xxxController 控制层,而它们在异常处理方面又存在着很多的共性,这样就不太适合在每个 xxxAction / xxxController 类中都嵌入一个对应的异常处理方法。因此我们可以将异常处理方法抽取到一个统一继承的基类中,然后所有的 xxxAction / xxxController 统一继承基类即可。

基类 BaseAction / BaseController:

public class BaseAction {    
@ExceptionHandler(Exception.class)    
public Result handleException(Exception e) {        
        return new ResponseDataVO(ResponseStatusCode.CUSTOMEXCEPTION);    
    }
}
// or
public class BaseController {    
    @ExceptionHandler(Exception.class)    
    public Result handleException(Exception e) {        
        return new ResponseDataVO(ResponseStatusCode.CUSTOMEXCEPTION);    
    }
}

UserController 通过继承 BaseAction / BaseController 完成异常处理:

@RestController@RequestMapping("/sys")
public class LoginController extends BaseAction {
    @PostMapping("/login")
    public ResponseDataVO login(@RequestBody UserLoginReqVO userLoginReqVO) {
        // 具体业务处理省略        
        ...        
        doSomething();    
    }
}
// or
@RestController
@RequestMapping("/sys")
public class LoginController extends BaseController {
    @PostMapping("/login")
    public ResponseDataVO login(@RequestBody UserLoginReqVO userLoginReqVO) {
        // 具体业务处理省略        
        ...        
        doSomething();    
    }
}

2. @ControllerAdvice 注解

对于使用基类 BaseAction / BaseController 完成统一异常处理也存在着缺点,那就是代码耦合度高,一旦开发人员编写 xxxAction / xxxController 时忘记继承 BaseAction / BaseController 基类,发生异常时又会直接抛到用户面前。如若想去除异常捕获的代码耦合度,我们可以使用 @ControllerAdvice 和 @ExceptionHandler 注解结合完成全局异常处理。

@ControllerAdvice
@ResponseBody
public class ExceptionHandlerAdvice {


    @ExceptionHandler(Exception.class)
    public ResponseDataVO handleException(Exception e) {
        return new ResponseDataVO(ResponseStatusCode.CUSTOMEXCEPTION);    
    }


}

在该类中,可以定义多个类别的异常捕获方法,不同的方法用于捕获不同类别的异常情况,例如专门捕获 SQL 异常的方法、专门捕获自定义异常的方法等等,也可以直接使用上面代码,在一个方法中处理所有的异常信息。

另外,在 ExceptionHandlerAdvice 类中的 @ExceptionHandler 注解是用来指明用户要捕获异常类型,这样发生异常时可以精确匹配到该方法,即如果这里指定为自定义异常类 CustomException,则其他异常就不会进到捕获自定义异常的方法中。

例如:

自定义异常类 BusinessException,代码如下:

public class BusinessException extends Exception {
    //业务类型
    private String bizType;
    //业务代码
    private int bizCode;
    //错误信息
    private String message;


    public BusinessException(String bizType, int bizCode, String message){
        super(message);
        this.bizType = bizType;
        this.bizCode = bizCode;
        this.message = message;
    }


    public BusinessException(String message){
        super(message);
        this.bizType = "";
        this.bizCode = -1;
        this.message = message;
    }


    public BusinessException(String bizType, String message){
        super(message);
        this.bizType = bizType;
        this.bizCode = -1;
        this.message = message;
    }


    public BusinessException(int bizCode, String message){
        super(message);
        this.bizType = "";
        this.bizCode = bizCode;
        this.message = message;
    }
    // 省略 getter/setter 方法
}

ExceptionHandlerAdvice 类中新增 businessException 方法捕获自定义异常,代码如下:

@ControllerAdvice
@ResponseBody
public class ExceptionHandlerAdvice {
    @ExceptionHandler(Exception.class)
    public ResponseDataVO handleException(Exception e) {
        return new ResponseDataVO(ResponseStatusCode.CUSTOMEXCEPTION);    
    }


    // 自定义异常捕获
    @ExceptionHandler(BusinessException.class)
    public Result businessException(BusinessException e) {
        return new ResponseDataVO(ResponseStatusCode.CUSTOMEXCEPTION);    
    }


}

小结

通过以上的学习,大家应该了解了 @ExceptionHandler 和 @ControllerAdvice 这两个注解的用法,对于全局异常处理有了一定的认识,为了方便对异常的统一管理,SpringMVC、Spring Boot 提供了 @ControllerAdvice 注解对异常进行统一的处理,拿到这些异常信息后,可以做一些处理,比如提供一个统一的 Web 界面查看异常信息,或者捕捉到异常信息后,以发送短信或是邮件形式通知到相关技术人员,可以帮助其开发人员快速发现并定位问题,减少以往通过查看线上日志文件排查问题繁琐过程的时间。而 @ControllerAdvice 注解的作用远远不止这些,还包括全局数据绑定和全局数据预处理等,有兴趣想了解更多相关信息的可以去 Spring 官方文档中查阅学习。

——> End <——

# 精彩推荐 #

持续集成系列(I)——享玩Docker之GitLab

JVM故障分析及性能优化实战(I)——使用jstack定位线程堆栈信息

内存泄漏 - 从Class类加载器说起

JVM 优化、内存泄露排查、gc.log 分析方法等

分布式 ID 生成器如何选择?

"在看",你呢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值