【发布时间】:2021-06-03 15:25:34
【问题描述】:
在将 AOP 与 AspectJ 一起使用时,我遇到了一种奇怪的行为。
基本上 @Around 方法被调用一次或两次,在尝试调试时我找不到它执行两次的原因(我的意思是触发该方法的第二次执行的原因)
这里有一些代码:
@Aspect
@Slf4j
public class ReactiveRedisCacheAspect {
@Pointcut("@annotation(com.xxx.xxx.cache.aop.annotations.ReactiveRedisCacheable)")
public void cacheablePointCut() {}
@Around("cacheablePointCut()")
public Object cacheableAround(final ProceedingJoinPoint proceedingJoinPoint) {
log.debug("ReactiveRedisCacheAspect cacheableAround.... - {}", proceedingJoinPoint);
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = methodSignature.getMethod();
Class<?> returnTypeName = method.getReturnType();
Duration duration = Duration.ofHours(getDuration(method));
String redisKey = getKey(method, proceedingJoinPoint);
if (returnTypeName.isAssignableFrom(Flux.class)) {
log.debug("returning Flux");
return cacheRepository.hasKey(redisKey)
.filter(found -> found)
.flatMapMany(found -> cacheRepository.findByKey(redisKey))
.flatMap(found -> saveFlux(proceedingJoinPoint, redisKey, duration));
} else if (returnTypeName.isAssignableFrom(Mono.class)) {
log.debug("Returning Mono");
return cacheRepository.hasKey(redisKey)
.flatMap(found -> {
if (found) {
return cacheRepository.findByKey(redisKey);
} else {
return saveMono(proceedingJoinPoint, redisKey, duration);
}
});
} else {
throw new RuntimeException("non reactive object supported (Mono,Flux)");
}
}
private String getKey(final Method method, final ProceedingJoinPoint proceedingJoinPoint) {
ReactiveRedisCacheable annotation = method.getAnnotation(ReactiveRedisCacheable.class);
String cacheName = annotation.cacheName();
String key = annotation.key();
cacheName = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, cacheName);
key = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, key);
return cacheName + "_" + key;
}
}
public class AspectSupportUtils {
private static final ExpressionEvaluator evaluator = new ExpressionEvaluator();
public static Object getKeyValue(JoinPoint joinPoint, String keyExpression) {
if (keyExpression.contains("#") || keyExpression.contains("'")) {
return getKeyValue(joinPoint.getTarget(), joinPoint.getArgs(), joinPoint.getTarget().getClass(),
((MethodSignature) joinPoint.getSignature()).getMethod(), keyExpression);
}
return keyExpression;
}
private static Object getKeyValue(Object object, Object[] args, Class<?> clazz, Method method, String keyExpression) {
if (StringUtils.hasText(keyExpression)) {
EvaluationContext evaluationContext = evaluator.createEvaluationContext(object, clazz, method, args);
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, clazz);
return evaluator.key(keyExpression, methodKey, evaluationContext);
}
return SimpleKeyGenerator.generateKey(args);
}
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReactiveRedisCacheable {
String key();
String cacheName();
long duration() default 1L;
}
@RestController
@RequestMapping("api/pub/v1")
public class TestRestController{
@ReactiveRedisCacheable(cacheName = "test-cache", key = "#name", duration = 1L)
@GetMapping(value = "test")
public Mono<String> getName(@RequestParam(value = "name") String name){
return Mono.just(name);
}
}
@Configuration
public class Config {
@Bean
public ReactiveRedisCacheAspect reactiveRedisCache (ReactiveRedisCacheAspect reactiveRedisCacheAspect) {
return reactiveRedisCacheAspect;
}
}
日志:
ReactiveRedisCacheAspect cacheableAround.... - {}execution(Mono com.abc.def.xxx.rest.TestRestcontroller.getName(String))
2021-06-04 15:36:23.096 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ctor-http-nio-3] c.m.s.c.a.i.ReactiveRedisCacheAspect : Returning Mono
2021-06-04 15:36:23.097 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ctor-http-nio-3] c.m.s.c.repository.CacheRepositoryImpl : searching key: (bff_pippo)
ReactiveRedisCacheAspect cacheableAround.... - {}execution(Mono com.abc.def.xxx.rest.TestRestcontroller.getName(String))
2021-06-04 15:36:23.236 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.a.i.ReactiveRedisCacheAspect : Returning Mono
2021-06-04 15:36:23.236 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.repository.CacheRepositoryImpl : searching key: (bff_pippo)
2021-06-04 15:36:23.250 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.repository.CacheRepositoryImpl : saving obj: (key:bff_pippo) (expiresIn:3600s)
2021-06-04 15:36:23.275 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.repository.CacheRepositoryImpl : saving obj: (key:bff_pippo) (expiresIn:3600s)
到目前为止,我预计 cacheableAround 只会执行一次,但发生的事情有点奇怪,如果对象存在于 redis 上,则该方法仅执行一次,但如果不存在,则该方法执行两次没有意义,而且应该是管理方法内部做什么的业务逻辑。
提前致谢!
【问题讨论】:
-
具有完整代码的可重现测试用例将有助于避免大量猜测工作。当对象不在缓存中时会发生什么?我认为它会尝试满足切入点表达式的回退。
-
行为非常简单,如果对象存在于缓存中,则返回它,否则保存它。正如我之前所说,这是业务逻辑的一部分,它不应该涉及“调用者”。没有回退,因为所有内容都封装在该方法中。我一直在查看源代码,或多或少的缓存注释与它实际上让我感到惊讶的是相同的,并且如果它被触发两次的方法和它不是一种感知而是一个真实的事实,我大多会感到困惑,而且我可以看到相同的保存操作在 Redis 上进行了两次,而查找操作只进行了一次。
-
某事应该触发目标方法调用两次,这会导致意外的建议。您是否尝试在建议方法处设置断点并检查调用堆栈以验证行为?
-
代码不完整。你能分享一下
getKey(method, proceedingJoinPoint)在做什么吗?分享前请检查代码完整性 -
AOP 不是问题。你的老板不是问题。我支持他的想法,即集中维护这个横切关注点的逻辑,而不是在所有现有和未来的服务中分散和重复它,这将是维护的噩梦。如果您使用它一段时间,AOP 并不难。就像每一个新范式一样,这只是一开始很困难。 “一开始可能看起来很困难,但所有事情一开始都很困难。”宫本武藏,五环之书
标签: spring-webflux aop aspectj spring-aop