springboot项目全局异常处理的三种方式

springboot项目全局异常处理的三种方式

背景

通过多次代码重构的经验积累,在最近一次从零开始的功能开发中,我决定完善项目的全局异常处理机制。项目中的主要应用场景分为两类:一是面向服务的控制层(如各类控制器),这类问题可以通过简单的 @ControllerAdvice@ExceptionHandler 注解来解决;二是各种异步定时任务,对于这类场景,上述方法不再适用,因此需要采用自定义切面的方式来实现全局异常处理。经过测试和总结,找到了三种有效的全局异常处理方法。

实现

控制层的全局异常@ControllerAdvice和@ExceptionHandler

package com.exception.handler.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletRequest;

/**
 * 统一处理控制器中的异常, 返回一个带有HTTP状态码和响应体的对象
 */
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(value = {Exception.class})
    public ResponseEntity<Object> handleAllUncaughtExceptions(Exception ex, HttpServletRequest request) {
        // 可以在这里添加日志记录或其他处理逻辑
        log.error("Caught an exception: " + ex.getMessage());
        // 返回一个自定义的错误消息和HTTP状态码
        return ResponseEntity.status(500).body("An unexpected error occurred: " + ex.getMessage());
    }

    @ExceptionHandler(value = {NullPointerException.class})
    public ResponseEntity<Object> handleNullPointerException(NullPointerException ex, WebRequest request) {
        // 特定于NullPointerException的处理逻辑
        return ResponseEntity.status(400).body("Null pointer exception occurred: " + ex.getMessage());
    }

    // 可以添加更多的异常处理器方法...
}

异步定时任务的全局异常

1.@AfterThrowing
package com.exception.handler.config;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class ExceptionHandlingAspect {
	// 匹配com.exception.handler包及其所有子包下的所有类中的所有方法的执行
    @AfterThrowing(pointcut = "execution(* com.exception.handler..*(..))", throwing = "e")
    public void handleException(JoinPoint joinPoint, Exception e) {
        // 在这里处理异常
        // 可以在这里记录日志或者发送邮件通知等
        log.error("[{}] occurs exception, error is {}, location is {}",
                joinPoint.getSignature().toShortString(), e.getMessage(), e.getStackTrace()[0]);
    }
}
2.自定义注解 MyExceptionGlobalCatch
package com.exception.handler.aspect;

import java.lang.annotation.*;

/**
 * 可作用类、方法的全局异常捕捉处理注解
 */
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyExceptionGlobalCatch {
}
package com.exception.handler.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Slf4j
@Component
public class MyExceptionGlobalCatchAspect {

    @Around("@annotation(com.exception.handler.aspect.MyExceptionGlobalCatch) || @within(com.exception.handler.aspect.MyExceptionGlobalCatch)")
    public Object exceptionHandler(ProceedingJoinPoint point) {
        Object result = null;
        String signature = point.getSignature().toShortString();
        try {
            result = point.proceed();
        } catch (Throwable e) {
            log.error("[{}] occurs exception, error is {}, location is {}", signature, e.getMessage(), e.getStackTrace()[0]);
        } finally {
            // 这里面可以写业务提交
            // 比如之前写的kafka的ack,通过point.getArgs()然后通过 instanceof 找到对应的参数调用提交方法
            // 时刻注意生产环境里高流量下打印日志的级别
            log.debug("后续处理完成");
        }

        return result;
    }
}

测试

对于控制层异常的全局处理测试

在这里插入图片描述

对于异步定时任务异常的全局处理测试

AfterThrowing测试

在这里插入图片描述

自定义注解测试

在这里插入图片描述

总结

  1. 使用 @ControllerAdvice 这种方式适用于控制器层的全局异常捕获与处理。它可以拦截控制器中的异常,并返回一个包含特定HTTP状态码及响应体的对象。

  2. 针对异步定时任务的全局异常处理:

    • 使用 @AfterThrowing 和自定义注解是一种可行的方法。然而,直接使用 @AfterThrowing 只能用于记录异常信息,但不会阻止异常在其他地方被重复记录,因此不太理想。
    • 更推荐的做法是利用自定义注解结合切面编程。这种方式通过将自定义注解应用于类级别,可以在实现的切面类中集中处理异常,确保异常只被记录一次。

    此外,如果项目中涉及到 Kafka 的主动监听功能,可以考虑将消息确认(acknowledgment)的提交逻辑纳入全局异常处理机制中。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值