diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java index 70f0c63122e6..995f824b3fb3 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java @@ -44,6 +44,8 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu private final Map, Boolean> eligibleBeans = new ConcurrentHashMap<>(256); + private final Map earlyBeanReferences = new ConcurrentHashMap<>(16); + /** * Set whether this post-processor's advisor is supposed to apply before @@ -84,8 +86,23 @@ public Class determineBeanType(Class beanClass, String beanName) { return beanClass; } + /** + * Expose the advised bean early for circular references. + * @since 7.1 + */ + @Override + public Object getEarlyBeanReference(Object bean, String beanName) { + Object proxy = postProcessAfterInitialization(bean, beanName); + this.earlyBeanReferences.put(beanName, AopProxyUtils.ultimateSingletonTarget(bean)); + return proxy; + } + @Override public Object postProcessAfterInitialization(Object bean, String beanName) { + Object earlyReference = this.earlyBeanReferences.remove(beanName); + if (earlyReference != null && earlyReference == AopProxyUtils.ultimateSingletonTarget(bean)) { + return bean; + } if (this.advisor == null || bean instanceof AopInfrastructureBean) { // Ignore AOP infrastructure such as scoped proxies. return bean; diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java index 14fae60c3d8c..ccca68721fc2 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java @@ -17,6 +17,7 @@ package org.springframework.scheduling.annotation; import java.lang.reflect.Method; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -29,8 +30,15 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; @@ -93,6 +101,24 @@ void invokedAsynchronouslyOnProxyTarget() { context.close(); } + @Test + void cacheEvictionWithEarlySelfReference() throws Exception { + try (AnnotationConfigApplicationContext context = + new AnnotationConfigApplicationContext(ConfigWithEarlySelfReference.class)) { + + SelfReferencingBean bean = context.getBean(SelfReferencingBean.class); + Cache cache = context.getBean(CacheManager.class).getCache("test"); + cache.put("key", "value"); + assertThat(bean.evictAsync("key").get(5, TimeUnit.SECONDS)).isNotSameAs(Thread.currentThread()); + assertThat(cache.get("key")).isNull(); + + cache.put("key", "value"); + bean.evict("key"); + assertThat(cache.get("key")).isNull(); + assertThat(bean.getSelf()).isSameAs(bean); + } + } + @Test void threadNamePrefix() { BeanDefinition processorDefinition = new RootBeanDefinition(AsyncAnnotationBeanPostProcessor.class); @@ -318,6 +344,54 @@ public void execute(Runnable r) { } + @Configuration(proxyBeanMethods = false) + @EnableAsync + @EnableCaching + static class ConfigWithEarlySelfReference { + + @Bean + SelfReferencingBean selfReferencingBean(ApplicationContext context) { + return new SelfReferencingBean(context); + } + + @Bean + CacheManager cacheManager() { + return new ConcurrentMapCacheManager("test"); + } + } + + + static class SelfReferencingBean implements InitializingBean { + + private final ApplicationContext context; + + private SelfReferencingBean self; + + SelfReferencingBean(ApplicationContext context) { + this.context = context; + } + + @Override + public void afterPropertiesSet() { + this.self = this.context.getBean(SelfReferencingBean.class); + } + + public SelfReferencingBean getSelf() { + return this.self; + } + + @Async + public CompletableFuture evictAsync(String key) { + this.self.evict(key); + return CompletableFuture.completedFuture(Thread.currentThread()); + } + + @CacheEvict(cacheNames = "test", key = "#key") + public void evict(String key) { + } + } + + @Configuration @EnableAsync static class ConfigWithExceptionHandler implements AsyncConfigurer {