Spring Webflux认证场景下Spring AOP异常问题的解决方法
Spring AOP与Webflux兼容问题解决
问题描述
我为日志功能启用了Spring AOP,并使用Webflux作为API入口。未经过ReactiveAuthenticationManager的接口(如login接口)运行正常,但当接口需要经过ReactiveAuthenticationManager时,会抛出以下异常:
java.lang.IllegalStateException: No MethodInvocation found: Check that an AOP invocation is in progress and that the ExposeInvocationInterceptor is upfront in the interceptor chain. Specifically, note that advices with order HIGHEST_PRECEDENCE will execute before ExposeInvocationInterceptor! In addition, ExposeInvocationInterceptor and ExposeInvocationInterceptor.currentInvocation() must be invoked from the same thread. at org.springframework.aop.interceptor.ExposeInvocationInterceptor.currentInvocation(ExposeInvocationInterceptor.java:74) ~[spring-aop-5.3.23.jar:5.3.23] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): *__checkpoint ⇢ Handler com.ums.api.contoller.Controller#getAllUser() [DispatcherHandler]
AOP类代码
public class LoggerAspect { @Pointcut("within(com.ums..*)") public void logEveryFunction() { } @Before(value = "logEveryFunction()") public void log(JoinPoint joinPoint) { log.info("Entering function {} in location {}", joinPoint.getSignature(), joinPoint.getSourceLocation()); } @After(value = "logEveryFunction()") public void logEnd(JoinPoint joinPoint) { log.info("Exiting function {}", joinPoint.getSignature()); } }
认证方法代码
public Mono<Authentication> authenticate(Authentication authentication) { return Mono.justOrEmpty(authentication.getCredentials().toString()) .map(value -> { return new UsernamePasswordAuthenticationToken( authentication.getName(), null, Arrays.asList(new SimpleGrantedAuthority("VIEW")) }); }
解决方案
核心原因
传统Spring AOP依赖ThreadLocal存储方法调用上下文,但Webflux的响应式模型会在请求处理过程中切换线程,导致ThreadLocal中的调用信息丢失,从而触发该异常。同步类型的通知(如@Before/@After)在响应式环境下无法正确处理线程切换。
具体解决步骤
切换到响应式AOP实现
使用@Around通知替代@Before/@After,并处理响应式返回类型(Mono/Flux),确保日志逻辑跟随响应流的生命周期:import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @Aspect @Component public class LoggerAspect { private static final Logger log = LoggerFactory.getLogger(LoggerAspect.class); @Pointcut("within(com.ums..*)") public void logEveryFunction() {} @Around("logEveryFunction()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { log.info("Entering function {} in location {}", joinPoint.getSignature(), joinPoint.getSourceLocation()); try { Object result = joinPoint.proceed(); // 根据返回类型绑定日志逻辑 if (result instanceof Mono) { return ((Mono<?>) result) .doOnSuccess(res -> log.info("Exiting function {}", joinPoint.getSignature())) .doOnError(err -> log.error("Function {} threw exception", joinPoint.getSignature(), err)); } else if (result instanceof Flux) { return ((Flux<?>) result) .doOnComplete(() -> log.info("Exiting function {}", joinPoint.getSignature())) .doOnError(err -> log.error("Function {} threw exception", joinPoint.getSignature(), err)); } else { // 非响应式方法直接记录 log.info("Exiting function {}", joinPoint.getSignature()); return result; } } catch (Throwable throwable) { log.error("Exception in function {}", joinPoint.getSignature(), throwable); throw throwable; } } }调整AOP拦截器顺序
不要将自定义切面的优先级设置为HIGHEST_PRECEDENCE,确保ExposeInvocationInterceptor在拦截器链中先于你的切面执行。如果需要自定义顺序,可通过@Order注解设置合理的优先级(比如@Order(1),确保低于默认的ExposeInvocationInterceptor优先级)。避免ThreadLocal依赖
传统Spring AOP的部分特性依赖ThreadLocal,在Webflux环境下应尽量避免使用。响应式AOP通过绑定到数据流的方式,不需要依赖线程局部变量,更适合响应式场景。
内容的提问来源于stack exchange,提问作者Akash Jain
相关产品推荐
相关产品推荐

