You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)在响应式环境下无法正确处理线程切换。

具体解决步骤

  1. 切换到响应式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;
            }
        }
    }
    
  2. 调整AOP拦截器顺序
    不要将自定义切面的优先级设置为HIGHEST_PRECEDENCE,确保ExposeInvocationInterceptor在拦截器链中先于你的切面执行。如果需要自定义顺序,可通过@Order注解设置合理的优先级(比如@Order(1),确保低于默认的ExposeInvocationInterceptor优先级)。

  3. 避免ThreadLocal依赖
    传统Spring AOP的部分特性依赖ThreadLocal,在Webflux环境下应尽量避免使用。响应式AOP通过绑定到数据流的方式,不需要依赖线程局部变量,更适合响应式场景。

内容的提问来源于stack exchange,提问作者Akash Jain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 00:00:35