为什么不用 @ControllerAdvice

Spring 提供了 @ControllerAdvice 注解,可以处理全局异常。

但是 @ControllerAdvice 只能捕获到抛给 Controller 的异常,如果我压根没用 Controller ,比方说我写的是 Webservice 接口,这个注解就不起作用啦。这种情况可以使用 AOP 来实现全局异常处理。

业务场景

最近在写 Webservice 接口,因为没有做全局异常处理,所以 logback 日志没有收集到运行时异常的堆栈信息。而 @ControllerAdvice 只能捕获到抛给 Controller 的异常,所以只能寻找别的方法。最后决定使用 AOP 来实现。

又因为 Webservice 接口使用了 @Transactional 管理事务,如果把异常捕获了,没有抛给 Spring,@Transactional 就不能回滚事务,所以只能通过 @AfterThrowing 来处理异常。这样既能捕获到异常,并调用 logback 接口打印日志,又能保证 @Transactional 可以回滚事务,两全其美。

实现代码

引入依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

定义注解

/**
 * 如果没有回滚事务的要求,只是单纯想打印异常的堆栈信息,使用此注解
 * @since 2022-4-29 17:29
 */
public @interface LogForNoTransaction {
}
/**
 * 如果使用了 @Transactional 管理事务,则使用此注解
 * @since 2022-4-22 11:03
 */
public @interface LogForTransaction {
}

实现切面

public class ExceptionHandlerAspect {
 
    @Pointcut("@annotation(com.zdww.image.annotation.LogForNoTransaction)")
    public void logForNoTransaction() {}
 
    @Pointcut("@annotation(com.zdww.image.annotation.LogForTransaction)")
    public void logForTransaction() {}
 
    /**
     * 如果没有回滚事务的要求,只是单纯想打印异常的堆栈信息,使用此方法
     * @param pjp
     * @return
     */
    @Around("logForNoTransaction()")
    public Object around(ProceedingJoinPoint pjp) {
        Object result = null;
        try {
            // 执行原有业务
            result = pjp.proceed();
        } catch (Throwable e) {
            String className = pjp.getTarget().getClass().getName();
            String methodName = pjp.getSignature().getName();
            log.error("在{}的{}中,发生了异常:", className, methodName, e);
            return Response.fail();
 
        return result;
    }
 
    /**
     * 如果使用了 @Transactional 管理事务,则使用此方法,此方法会将异常抛给 Spring,Spring 就能回滚事务了
     * @param point
     * @param e
     * @throws Exception
     */
    @AfterThrowing(pointcut = "logForTransaction()", throwing = "e")
    public void handle(JoinPoint point, Exception e) throws Exception {
        String className = point.getTarget().getClass().getName();
        String methodName = point.getSignature().getName();
        log.error("在{}的{}中,发生了异常:", className, methodName, e);
        throw new Exception("系统发生了异常", e);
    }
}