0
点赞
收藏
分享

微信扫一扫

010-全局异常

1.异常举例

1.1. Exception

@ResponseBody
@GetMapping(value = "/testThrowException")
public RespJsonData<Integer> testThrowException() throws Exception {
  throw new Exception("测试异常失败testThrowException");
}

010-全局异常_异常类型

1.2. RuntimeException

1.2.1. 非法参数异常IllegalArgumentException

@ResponseBody
@GetMapping(value = "/testRunTimeException")
public RespJsonData<Integer> testRunTimeException(){
  int age = -5;
  if(age < 0){
      throw new IllegalArgumentException("年龄不能为负数");
  }
   return RespJsonData.success(age);
}

010-全局异常_自定义_02

1.2.2. 数据库相关的异常

DemoController下新增方法

@ResponseBody
@PostMapping(value = "/testInsert")
public RespJson testInsert(@RequestBody DemoEntity demoEntity){
 return demoService.testInsert(demoEntity);
}

Service

RespJson testInsert(DemoEntity demoEntity);

Impl

@Override
public RespJson testInsert(DemoEntity demoEntity) {
   demoMapper.insertSelective(demoEntity);
   demoMapper.insertSelective(demoEntity);
   return RespJson.success();
}

010-全局异常_自定义_03

1.3.总结

通过案例我们可以看出我们在程序开发中可能会遇到各种各样的异常情况,不通的异常我们所要进行处理方式又有所不同,这样导致了代码的可读性下降并且难以维护,同时增加了代码的冗余度。为了解决这样的问题,正好引申出我们将所要讲解的知识点"全局异常处理机制"。

2.什么是全局异常处理?

全局异常处理是一种机制,通过该机制可以统一捕获和处理应用程序中的异常,而不是在每个方法或控制器中单独处理异常。在Spring Boot中,我们可以使用@ControllerAdvice注解或者@RestControllerAdvice标记一个类作为全局异常处理器,然后在该类中定义不同类型异常的处理方法,用于集中处理我们在程序中所发生的异常信息,实现代码的简洁和可维护性。

3.全局异常处理的优势有哪些

  • 集中化处理:全局异常处理通过提供统一的异常处理逻辑,将异常的捕获和处理过程集中在一处。这样可以简化代码,避免在多个地方重复编写相同的异常处理代码。减少了代码冗余和重复劳动
  • 统一的错误响应:通过全局异常处理,可以为应用程序定义统一的错误响应格式。无论是未知的异常还是业务异常,都可以返回统一的错误信息,便于客户端或其他系统对异常进行处理。
  • 提高程序健壮性:全局异常处理可以捕获并处理未被预料到的异常情况,防止程序崩溃或出现未知错误。通过统一的异常处理逻辑,可以更好地保护程序的稳定性和可靠性。
  • 更好的用户体验:通过全局异常处理,可以为用户提供更友好、更具有可读性的错误信息。这有助于用户理解出现的错误,并可以给出相应的建议或解决方案,提升用户体验。
  • 方便的错误日志记录:全局异常处理可以方便地记录异常信息到日志系统中,从而有助于对系统异常进行排查和故障定位。通过异常日志,可以及时发现并解决潜在的问题。

4.如何设计全局异常处理

4.1. 设计全局异常处理的一般步骤如下

1.创建自定义异常类:创建自定义异常类,该类继承自RuntimeException或其子类。我们可以根据业务需求创建不同的自定义异常类,每个类代表一个特定类型的异常。

2.创建全局异常处理器:创建一个全局异常处理器类,并使用@ControllerAdvice注解或者@RestControllerAdvice标记。在这个类中,使用@ExceptionHandler注解定义处理不同异常的方法。

3.扫描全局异常处理器:为了使全局异常处理器生效,我们需要确保它能够被Spring Boot应用程序扫描到。可以将它放在主应用程序类的同一包或子包下,或者使用@ComponentScan注解显式指定扫描的包。

4.2. 在设计时中我们应注意什么

在设计异常时,关注异常的可读性、继承关系、粒度、可重用性、处理与日志、向上抛出和向下处理、可测试性以及文档说明是十分重要的。这样可以提高代码的可读性、可维护性和健壮性,并最大程度地减少潜在的错误。

  1. 异常的可读性和表达性:异常名称应该清晰地表达出异常的类型和具体情况,使其易于理解和识别。使用明确的命名约定,可以帮助其他开发人员快速理解异常并采取适当的措施。
  2. 异常的继承关系:合理地使用继承来组织异常类型的层次结构。常见的做法是定义一个基本的异常类(如 RuntimeException 或其子类),然后定义特定类型的异常作为其子类。这样可以更好地组织和分类异常,以便更好地处理和捕获不同类型的错误。
  3. 异常的粒度:异常应该根据具体情况来确定其粒度。过细粒度的异常可能会导致繁琐的异常处理代码,而过粗粒度的异常可能会隐藏有用的细节。选择适当的粒度可以提供更高的可读性和可维护性。
  4. 异常的可重用性:尽量设计可重用的异常,以便在不同的上下文中使用。通过定义通用的异常类型,可以在多个模块或层之间共享和使用相同的异常。
  5. 异常的处理与日志:异常处理应该包括适当的错误处理和日志记录。捕获并处理异常后,应考虑对异常进行适当的记录,以便更好地进行故障排除和问题定位。
  6. 异常的向上抛出和向下处理:在适当的时候,可以选择将异常向上抛出,让调用方处理异常。另外,也应该为可能抛出的异常提供合适的处理机制,以避免异常漏洞。
  7. 异常的可测试性:异常应该是可测试的,以便在单元测试和集成测试中捕获和验证异常的行为。这有助于确保异常被适当地抛出和处理。
  8. 异常的文档说明:在定义异常时,应提供相应的文档说明,包括异常的原因、可能的解决方案以及异常的使用和处理方式。这有助于其他开发人员更好地理解和使用异常。

4.3. 哪些设计会对异常处理有影响

在使用@ExceptionHandler注解定义异常处理方法时,方法定义的顺序会对异常处理产生影响。具体表现如下:

  1. 优先匹配最近匹配原则(LIFO):
  • 当多个异常处理方法可以处理同一类型的异常时,Spring框架会按照方法定义的顺序选择最近匹配(LIFO,Last-In-First-Out)的异常处理方法进行处理。
  • 换句话说,先定义的异常处理方法会被优先匹配和调用。
  1. 同一异常类型的匹配:
  • 当出现多个异常处理方法针对同一异常类型进行处理时,Spring框架会根据方法定义的顺序选择最近匹配的一个进行处理。
  • 后定义的异常处理方法会覆盖先定义的异常处理方法,只有一个方法会被选中进行处理。

@RestControllerAdvice
public class GlobalExceptionHandler {

   @ExceptionHandler(CustomException.class)
   public RespJson handleCustomException(CustomException ex) {
       // 自定义异常处理逻辑 1
       return RespJson.fail(e.getResponseCode(),e.getResponseMessage());
  }

  @ExceptionHandler(CustomException.class)
   public RespJson handleCustomException(CustomException ex) {
       // 自定义异常处理逻辑 2
       return RespJson.fail(e.getResponseCode(),e.getResponseMessage());
  }
}

在上述示例代码中,出现了两个针对相同异常类型 CustomException 的异常处理方法。根据方法定义的顺序,第二个方法会覆盖第一个方法,只有第二个方法会被调用。

因此,为了正确处理异常,请确保在异常处理方法中按照逻辑顺序进行定义,以确保所期望的异常处理方法被正确调用。如果有多个处理同一类型异常的方法,应确保它们之间的顺序关系满足应用的需求。

4.4. 在烛火博客中我们则如何实现的

4.4.1. 受检查异常(checked Exception) vs 非受检查异常(Unchecked Exception)

4.4.1.1. 什么是受检查异常

受检查异常(Checked Exception):

  • 是指编译时必须处理的异常,即在编译阶段强制要求程序员进行异常处理或者声明抛出。
  • 通常表示程序运行过程中可能遇到的可预见异常,如文件不存在、网络连接失败等。
  • 受检查异常是Exception类及其子类(但不包括RuntimeException及其子类)的异常。

4.4.1.2. 什么是非受检查异常

非受检查异常(Unchecked Exception)或运行时异常(RuntimeException):

  • 是指不被编译器要求强制进行异常处理或声明抛出的异常。
  • 通常表示程序运行过程中的错误或意外情况,如空指针引用、数组越界等。
  • 非受检查异常是RuntimeException类及其子类的异常。

4.4.1.3. 俩异常有什么区别

  • 对于受检查异常,编译器要求在代码中进行异常处理或者在方法签名中声明抛出,否则编译会报错。
  • 非受检查异常在编译时不会被强制要求进行处理或声明抛出,但在运行时仍然可以捕获并处理。

4.4.1.4. 处理不同异常时候的建议

无论是受检查异常还是非受检查异常,处理异常的方式主要有两种:使用try-catch块捕获和处理异常,或者使用throws关键字声明将异常抛出给上层调用者。

合理地处理异常是编写高质量、可靠性和稳定性的代码的重要组成部分。对于受检查异常,必须根据实际情况进行处理或声明抛出;对于非受检查异常,虽然不强制要求处理,但也建议在合适的地方进行捕获和处理,以保证程序的正常运行。

4.4.2. 全局异常相关实现

4.4.2.1. BaseException

BaseException:提取异常接口信息方便异常中快速获取返回码和返回消息

public interface BaseException {

   /**
    * 返回异常信息
    * @return
    */
   String getResponseMessage();

   /**
    * 返回异常编码
    * @return
    */
   String getResponseCode();
}

4.4.2.2. BaseCheckedException

创建一个受检查异常的抽象类BaseCheckedException继承Exception并实现BaseException

public abstract  class BaseCheckedException extends Exception implements BaseException {

   /** 异常信息 */
   protected String responseMessage;

   /** 异常码*/
   protected String responseCode;

   public BaseCheckedException(String responseCode, String responseMessage) {
       super(responseMessage);
       this.responseCode = responseCode;
       this.responseMessage = responseMessage;
  }

   public BaseCheckedException(String responseCode, String format, Object... args) {
       super(String.format(format, args));
       this.responseCode = responseCode;
       this.responseMessage = String.format(format, args);
  }

   @Override
   public String getResponseMessage() {
       return responseMessage;
  }

   public void setResponseMessage(String responseMessage) {
       this.responseMessage = responseMessage;
  }

   @Override
   public String getResponseCode() {
       return responseCode;
  }

   public void setResponseCode(String responseCode) {
       this.responseCode = responseCode;
  }
}

4.4.2.3. BaseUncheckedException

创建一个非受检查异常的抽象类BaseUncheckedException继承RuntimeException并实现BaseException

public abstract class BaseUncheckedException extends RuntimeException implements BaseException {


   /** 异常信息 */
   protected String responseMessage;

   /** 异常码*/
   protected String responseCode;

   /**
    * <h2>自定义返回码异常</h2>
    * @param errResp {@link ResponseCode} 返回码接口 具体内容需查看子实现
    */
   public BaseUncheckedException(ResponseCode errResp) {
       super(errResp.getResponseMessage());
       this.responseMessage = errResp.getResponseMessage();
       this.responseCode = errResp.getResponseCode();
  }

   /**
    * <h2>多参数校验异常构造</h2>
    * @param responseCode   返回码
    * @param format         格式化
    * @param args           参数集合
    */
   public BaseUncheckedException(String responseCode, String format, Object... args) {
       super(String.format(format, args));
       this.responseCode = responseCode;
       this.responseMessage = String.format(format, args);
  }


   @Override
   public String getResponseMessage() {
       return responseMessage;
  }

   public void setResponseMessage(String responseMessage) {
       this.responseMessage = responseMessage;
  }

   @Override
   public String getResponseCode() {
       return responseCode;
  }

   public void setResponseCode(String responseCode) {
       this.responseCode = responseCode;
  }
}

4.4.2.4. BizException

定义一个自定义业务异常继承BaseUncheckedException

public class BizException extends BaseUncheckedException {

   /**
    * <h2>自定义返回码异常</h2>
    * @param errResp {@link ResponseCode} 返回码接口 具体内容需查看子实现
    */
   public BizException(ResponseCode errResp) {
       super(errResp);
  }

   /**
    * <h2>多参数校验异常构造</h2>
    * @param responseCode   返回码
    * @param format         格式化
    * @param args           参数集合
    */
   public BizException(String responseCode, String format, Object... args) {
       super(responseCode, format, args);
  }
}

4.4.2.5. GlobalExceptionHandler

创建全局异常处理器GlobalExceptionHandler用于处理不同的异常类型,在异常处理里面我们暂定处理3种异常Exception,RuntimeException,和BizException

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler(Exception.class)
    public RespJson handleException(Exception e){
        log.error("Exception异常执行 >>  message:{},e:{}",e.getMessage(), e);
        return RespJson.fail();
    }

    @ExceptionHandler(BizException.class)
    public RespJson handleException(BizException e){
        log.error("BizException异常执行 >>  message:{},e:{}",e.getMessage(), e);
        return RespJson.fail(e.getResponseCode(),e.getResponseMessage());
    }


    @ExceptionHandler(RuntimeException.class)
    public RespJson handleRuntimeException(RuntimeException e){
        log.error("RuntimeException异常执行 >>  message:{},e:{}",e.getMessage(), e);
        return RespJson.fail(e.getMessage());
    }

}

4.4.3. 代码改造后相关验证

4.4.3.1. 针对Exception异常的处理

010-全局异常_异常类型_04

4.4.3.1. 针对BizException异常的处理

010-全局异常_自定义_05

4.4.3.1. 针对RunTimeException异常的处理

010-全局异常_自定义_06

4.5. 全局异常拓展

全局异常处理是一种灵活而强大的机制,了以上我们在烛火博客中所实现的基本用法外,还可以根据实际需求进行扩展以提供更多的灵活性。下面是一些扩展和增强全局异常处理的方法,有兴趣的可以自己进行下相关验证,我们在本堂课程中仅提供一些相关思路

  1. 异常类型的细化和处理:
  • 根据具体的业务需求,可以定义多个异常类型并相应地处理。
  • 创建自定义的异常类,继承自标准的异常类,用于表示特定类型的异常。
  • 在全局异常处理器中,定义针对不同异常类型的处理方法,以实现更细致化的异常处理。
  1. 全局异常处理器的多级配置:
  • 可以创建多个全局异常处理器类,每个类负责处理不同范围、不同模块或不同层级的异常。
  • 组织全局异常处理器的级别和顺序,以便在异常发生时按照预定的优先级逐级处理异常。
  1. 异常处理链的形成:
  • 全局异常处理机制允许形成异常处理链,即多个异常处理器相互配合处理异常。
  • 在处理异常的方法中,可以选择将异常传递给下一个异常处理器进行处理,从而形成处理链。
  1. 异常处理器的条件匹配:
  • 可以使用条件判断来实现不同条件下的异常处理。
  • 使用条件注解(如@ConditionalOnProperty@ConditionalOnExpression)来决定是否激活或禁用特定的异常处理器。
  1. 全局异常处理器的定制化:
  • 可以根据具体需求,对全局异常处理器进行自定义和定制化。
  • 继承Spring框架提供的默认全局异常处理器,重写相关方法,并利用注解、配置等进行定制。
  1. 异常处理器的优先级:
  • 可以为每个异常处理器指定优先级,以确保按照特定的处理顺序进行异常处理。
  • 使用@Order注解或实现Ordered接口来指定异常处理器的优先级。



举报

相关推荐

0 条评论