spring-projects / spring-projects/spring-framework
EnableAsync prevents EnableCaching from working in self-injection scenarios [SPR-15915]
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 60.2k
- Forks
- 38.8k
- Avg merge
- 5d 2h
- Merged PRs (30d)
- 27
Description
Henri Tremblay opened SPR-15915 and commented
If I use @Async and @CacheEvict on the same class, calling the method with @CacheEvict directly won't evict anymore.
Let's say I have the following class.
public class MyClass {
private CountDownLatch latch;
private MyClass meWithAProxy;
@Autowired
ApplicationContext applicationContext;
@PostConstruct
public void init() {
meWithAProxy = applicationContext.getBean(MyClass.class);
}
public CountDownLatch getLatch() {
return latch;
}
public void setLatch(CountDownLatch latch) {
this.latch = latch;
}
@Async
public void function1() {
meWithAProxy.anotherFunction(123);
if(latch != null) {
latch.countDown();
}
}
@CacheEvict(cacheNames = "cache", key = "#testId")
public List<Integer> anotherFunction(int testId) {
return Collections.emptyList();
}
}
And I then use it like that:
@Configuration
@EnableCaching
@EnableAsync
public class App {
public static void main(String[] args) throws InterruptedException {
ApplicationContext context = new AnnotationConfigApplicationContext(App.class);
CacheManager cacheManager = context.getBean(CacheManager.class);
Cache cache = cacheManager.getCache("cache");
MyClass myClass = context.getBean(MyClass.class);
cache.put(123, "test"); // value to evict
myClass.setLatch(new CountDownLatch(1));
myClass.function1(); // this is correctly called asynchronously
myClass.getLatch().await();
assertThat(cache.get(123)).describedAs("Reentrant call failed").isNull(); // and the value is evicted as expected
cache.put(1, "test"); // new value to evict
assertThat(cache.get(1)).isNotNull();
myClass.anotherFunction(1); // direct call
assertThat(cache.get(1)).describedAs("Direct call failed").isNull(); // fails!
}
@Bean
public MyClass myClass() {
return new MyClass();
}
@Bean
public TaskExecutor taskExecutor() {
return new SimpleAsyncTaskExecutor();
}
@Bean
public CacheManager cacheManager() {
javax.cache.CacheManager cacheManager = Caching.getCachingProvider().getCacheManager();
cacheManager.createCache("cache", new MutableConfiguration<>().setStoreByValue(false));
return new JCacheCacheManager(cacheManager);
}
}
For some reason, the cache interceptor is not there. It seems that the Advisors with the cache are replaced by the async ones but I don't know why.
Affects: 4.3.10
Issue Links:
- #18488 Scheduled method is not invoked via proxy
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the @EnableAsync and @EnableCaching entry points and reproduce the behavior using the MyClass and App configuration shown in the issue. Trace how the async and cache advisors are applied, then verify that both the asynchronous call and the direct anotherFunction call evict the expected cache entry.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100