spring boot 全局异常处理无法直接捕获切面织入异常,因为 @controlleradvice 的异常处理器执行时机晚于 aop 拦截器链。要实现该功能,必须将全局异常处理逻辑注册为 aop 拦截器(实现 handlerexceptionresolver 或 ordered 接口),并确保其优先级高于其他切面,从而在异常抛出时优先接管。

为什么 @ControllerAdvice 抓不到切面异常
当请求进入 Spring MVC 流程时,AOP 切面通过动态代理包裹目标方法。如果切面内部抛出了未捕获的异常,该异常会沿着调用栈向上传播。此时,@ControllerAdvice 中的 @ExceptionHandler 尚未介入,因为它的执行依赖于 DispatcherServlet 在请求映射阶段后的异常解析机制。若切面先于异常解析器抛出异常,且未被切面自身捕获,程序通常会直接返回 500 错误页面,而不会触发全局异常处理的逻辑。

如何编写高优先级的异常拦截器
要解决这个问题,需要自定义一个实现了 HandlerExceptionResolver 接口的 Bean。这个 Bean 会在 Spring 尝试解析异常时被调用。关键在于设置其优先级(Order),使其数值小于默认的 @ControllerAdvice 优先级(默认为 Integer.MAX_VALUE)。这样,当切面抛出异常时,该拦截器会首先被命中,从而有机会记录日志、转换异常类型或返回特定的 JSON 响应,而不是让异常继续传播导致服务崩溃。
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
// 这里可以捕获切面或其他地方抛出的异常
if (ex instanceof RuntimeException) {
try {
response.setStatus(400);
response.getWriter().write("Caught by Custom Resolver: " + ex.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
return new ModelAndView();
}
return null; // 返回 null 表示不处理,交给下一个解析器
}
}
上述代码定义了一个名为 CustomExceptionResolver 的组件。它使用了 @Order(Ordered.HIGHEST_PRECEDENCE) 注解,确保其在所有其他异常解析器之前执行。在 resolveException 方法中,我们检查异常类型并写入响应。如果返回 null,则异常会继续传递给下一个解析器,如 @ControllerAdvice。这种机制允许我们在切面层之后、视图渲染之前拦截异常。
基于具体输入条件的异常处理验证
为了验证上述方案的有效性,我们可以构造一个具体的测试场景。假设有一个切面 LoggingAspect,它在执行目标方法前检查参数。如果参数为空,切面主动抛出 IllegalArgumentException。同时,我们有一个全局异常处理器 GlobalExceptionHandler 处理 RuntimeException。由于 IllegalArgumentException 不是 RuntimeException 的子类(它是 IllegalArgumentException,属于运行时异常,但通常 @ControllerAdvice 只匹配特定类型),我们需要确认我们的自定义解析器是否能捕获它。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
if (args.length == 0 || args[0] == null) {
throw new IllegalArgumentException("Parameter cannot be null");
}
return joinPoint.proceed();
}
}
在这个例子中,LoggingAspect 是切面。当调用 com.example.service.TestService.testMethod(null) 时,切面会抛出 IllegalArgumentException。由于我们的 CustomExceptionResolver 设置了最高优先级,它会首先捕获这个异常。如果我们将 CustomExceptionResolver 中的判断改为 ex instanceof IllegalArgumentException,它将成功捕获并处理该异常,返回 400 状态码和自定义消息。这证明了通过调整异常解析器的优先级和类型匹配,可以有效处理切面织入的异常。


















