一、抛异常的流程

在Spring Boot(或Spring MVC)的应用中,异常确实通常会按照从Mapper层抛给Service层,再抛给Controller层,最终由Spring MVC的DispatcherServlet来参与整个异常处理流程的一部分(尽管DispatcherServlet本身并不直接处理异常,但它负责调用控制器并处理控制器的响应,包括异常处理后的响应)

二、Spring MVC的异常处理机制

了解上面的流程,当Controller层抛出异常时,Spring MVC的异常处理机制会介入。它会查找是否有全局异常处理器(或控制器中定义的局部异常处理方法(使用@ExceptionHandler注解的方法)来处理这个异常。如果找到了相应的异常处理方法,Spring MVC会调用这个方法来处理异常,并生成一个HTTP响应返回给客户端。如果没有找到相应的异常处理方法,Spring MVC可能会使用默认的异常处理策略(如返回错误页面或JSON格式的错误信息)。

 三、自定义异常

我们以这个自定义异常为例:

@Override
public void deleteById(Long id) {
    Serve serve = baseMapper.selectById(id);
    if(ObjectUtil.isNull(serve)){
        throw new ForbiddenOperationException("信息不存在");
    }
        
   //业务代码
}

首先我的项目中让ForbiddenOperationException()继承CommonException(),其次CommonException继承自RuntimeException,继承RuntimeException的好处,有下面四点:

  1. 未受检异常:RuntimeException及其子类属于未受检异常,这意味着它们在编译时不需要被显式捕获或声明抛出。这减少了代码的冗余,使得开发者可以更加专注于业务逻辑的实现。
  2. 事务管理:在Spring框架等事务管理环境中,默认情况下只有未捕获的RuntimeException会触发事务回滚。因此,如果自定义异常旨在表示程序中的逻辑错误或意外情况,并且希望这些错误能够导致事务回滚,那么继承自RuntimeException是一个合适的选择。
  3. 简化代码:由于未受检异常不需要在方法签名中声明,因此使用继承自RuntimeException的自定义异常可以简化代码结构,提高代码的可读性和可维护性。
  4. 全局异常处理:在Spring Boot等现代Java框架中,开发者通常会使用全局异常处理器来捕获和处理各种异常。继承自RuntimeException的自定义异常可以更容易地被全局异常处理器捕获和处理,从而提供一致的异常处理体验。
public class ForbiddenOperationException extends CommonException {
    public ForbiddenOperationException() {
        this("禁止操作");
    }

    public ForbiddenOperationException(String message) {
        super(604, message);
    }

    public ForbiddenOperationException(Throwable throwable, String message) {
        super(throwable, 604, message);
    }

    public ForbiddenOperationException(Throwable throwable) {
        super(throwable, 604, "禁止操作");
    }
}

查看上方的异常处理机制,他会去寻找是否有全局异常处理器,或控制器中定义的局部异常处理方法(使用@ExceptionHandler注解的方法)来处理这个异常。

关键的两个注解

第一个是@RestControllerAdvice,是一个Spring MVC的注解,它用来定义全局的异常处理逻辑。你可以把它想象成一个“全局异常处理助手”。当你在Spring应用中遇到异常时,这个助手会自动站出来,帮你处理这些异常,而不是让每个控制器(Controller)都去单独处理。

另一个是@ExceptionHandler,这个注解可以理解为:当你使用@ExceptionHandler注解在一个方法上时,你就告诉Spring:“嘿,当遇到这种类型的异常时,就用这个方法来处理它”。这个方法可以接收异常对象作为参数,并且可以返回一个响应给客户端,比如一个JSON格式的错误信息。

@RestControllerAdvice
@Slf4j
public class CommonExceptionAdvice {

    /**
     * 自定义异常处理
     * @param e
     * @return
     */
    @ExceptionHandler({CommonException.class})
    public Result customException(CommonException e) {
        log.error("请求异常,message:{},e", e.getMessage(),e);
        // 标识异常已被处理
        ResponseUtils.setResponseHeader(BODY_PROCESSED, "1");
        if(RequestUtils.getRequest().getRequestURL().toString().contains("/inner/")) {
            CommonException commonException = new CommonException(e.getCode(), e.getMessage());
            ResponseUtils.setResponseHeader(HeaderConstants.INNER_ERROR, Base64Utils.encodeStr(e.getCode() + "|" + e.getMessage()));
            throw commonException;
        }
        return Result.error(e.getCode(), e.getMessage());
    }

    /**
     * 非自定义异常处理
     * @param e 异常
     * @return
     */
    @ExceptionHandler({Exception.class})
    public Result noCustomException(Exception e) {
        log.error("请求异常,", e);
        // 标识异常已被处理
        ResponseUtils.setResponseHeader(BODY_PROCESSED, "1");
        if(RequestUtils.getRequest().getRequestURL().toString().contains("/inner/")) {
            CommonException commonException = new CommonException(ErrorInfo.Msg.REQUEST_FAILD);

            ResponseUtils.setResponseHeader(HeaderConstants.INNER_ERROR, Base64Utils.encodeStr( "500|" + ErrorInfo.Msg.REQUEST_FAILD));
            throw commonException;
        }
        return Result.error(ErrorInfo.Msg.REQUEST_FAILD);
    }
    
}

可能会有疑惑,我抛出的是new ForbiddenOperationException(),异常处理器中没写处理其的方法,也可以处理吗?

那你还记不记得,他继承自CommonException,因为Java的异常处理机制遵循继承原则。如果一个异常是某个特定异常类的实例,或者是这个特定异常类的任何子类的实例,那么任何捕获这个特定异常类(或其父类)的catch块,或者在这个场景下是Spring MVC中的@ExceptionHandler方法,都能够处理这个异常。

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐