我有一个 Enabled Spring AOP 用于我的日志记录目的,并使用 webflux 作为 API 入口点, 如果 api 没有像我的
ReactiveAuthenticationManager
api 那样通过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"))
});
}
如何让我的 AOP 与 webflux 一起工作,我该如何解决这个问题?
让我们再读一遍错误信息:
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.
所以让我们尝试在我们的方面添加一个
@Order
注释。但首先,进行一些研究。你可以查看Javadocs,我只是查看了源代码:
public @interface Order {
int value() default Ordered.LOWEST_PRECEDENCE;
}
public interface Ordered {
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
int getOrder();
}
结论:默认顺序为
LOWEST_PRECEDENCE
,即Integer.MAX_VALUE
。因此,让我们分配一个更高的优先级,即比Integer.MAX_VALUE
小的东西。对于您在 GitHub 上的简单示例,以下任何一项都可以:@Order(Integer.MAX_VALUE - 1)
、@Order(0)
或您认为适合您的情况的任何内容。将其添加到您的方面类后,调用者将在控制台上看到:
$ curl -L "http://localhost:8080/login" -H "Authorization: Bearer ghfjhgf"
HI
Spring 服务器日志会说:
[ restartedMain] c.a.s.SpringBootWebfluxJjwtApplication : Started SpringBootWebfluxJjwtApplication in 1.646 seconds (JVM running for 2.156)
[ restartedMain] o.s.b.a.ApplicationAvailabilityBean : Application availability state LivenessState changed to CORRECT
[ restartedMain] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
...
[oundedElastic-1] c.a.s.rest.LoggerAspect : Entering function Mono com.ard333.springbootwebfluxjjwt.rest.AuthenticationREST.login() in location org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@641fcca3
[oundedElastic-1] c.a.s.rest.LoggerAspect : Exiting function Mono com.ard333.springbootwebfluxjjwt.rest.AuthenticationREST.login()
更新,回复后续问题:
我仍然想知道为什么会这样,为什么需要
?只有一个建议(来自我的 AOP)哪个会运行?@Order
如果您从返回
@PreAuthorize
的登录方法中删除 Mono
,它可以在方面没有 @Order
注释的情况下工作。这给了你一个线索:Spring Security 似乎也使用 AOP 或至少某种方法拦截器。让我们更详细地看看实际错误:
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.7.jar:5.3.7]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ Handler com.ard333.springbootwebfluxjjwt.rest.AuthenticationREST#login() [DispatcherHandler]
|_ checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.authentication.logout.LogoutWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
|_ checkpoint ⇢ HTTP GET "/login" [ExceptionHandlingWebHandler]
Stack trace:
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.currentInvocation(ExposeInvocationInterceptor.java:74) ~[spring-aop-5.3.7.jar:5.3.7]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.getJoinPointMatch(AbstractAspectJAdvice.java:658) ~[spring-aop-5.3.7.jar:5.3.7]
at org.springframework.aop.aspectj.AspectJMethodBeforeAdvice.before(AspectJMethodBeforeAdvice.java:44) ~[spring-aop-5.3.7.jar:5.3.7]
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:57) ~[spring-aop-5.3.7.jar:5.3.7]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.7.jar:5.3.7]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.7.jar:5.3.7]
at org.springframework.security.access.prepost.PrePostAdviceReactiveMethodInterceptor.proceed(PrePostAdviceReactiveMethodInterceptor.java:156) ~[spring-security-core-5.5.0.jar:5.5.0]
at org.springframework.security.access.prepost.PrePostAdviceReactiveMethodInterceptor.lambda$invoke$4(PrePostAdviceReactiveMethodInterceptor.java:116) ~[spring-security-core-5.5.0.jar:5.5.0]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125) ~[reactor-core-3.4.6.jar:3.4.6]
(...)
请注意堆栈跟踪中的
org.springframework.security.access.prepost.PrePostAdviceReactiveMethodInterceptor
。显然,在 Spring Security 中有一个针对响应式方法的特殊方法拦截器。如您和我之前发布的错误消息中所述,此拦截器似乎需要对拦截器/通知链进行特殊排序。我想,这是某种引导问题,希望结合使用 Spring AOP、Spring Security 和 WebFlux 的用户需要适应。