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
Reference URL: https://stackoverflow.com/questions/45938279/cache-not-refreshing-when-being-called-from-a-asynchrounous-function-in-spring/45963494#45963494
Issue Links: - #18488 Scheduled method is not invoked via proxy
Comment From: spring-projects-issues
Juergen Hoeller commented
This is strongly related to self-injection: That self-injected early bean reference gets flagged as such in AbstractAutoProxyCreator
, expecting to see a pre-proxied instance in postProcessAfterInitialization
which it doesn't have to add any interceptors to anymore. While this is an accurate assumption in most cases, it can lead to accidental exposure of an incomplete proxy in self-injection scenarios.
Comment From: spring-projects-issues
Juergen Hoeller commented
It's generally a good idea to break such a self-reference initialization cycle with an @Lazy
reference (or corresponding ObjectFactory
/Provider
declaration), in this case for the meWithAProxy
reference. We'll see what we can do to make it work without that as well but probably only in 5.x.
Comment From: anishlukk123
Is there anyone working on this?
Comment From: amitarcade
Hi, I would like to contribute on it, pls assign to me, thanks
Comment From: amitarcade
When I tested, it works fine : Code:
@Service @CacheConfig public class Util {
Util util;
@Autowired
ApplicationContext appCont;
@Cacheable(value = "pi", key = "#inp")
public int calPi(int inp) {
System.out.println("inp "+inp);
return inp * 3;
}
@PostConstruct
public void init() {
util = appCont.getBean(Util.class);
System.out.println("POST construct "+util.getClass());
}
@Async
public void methodOne() {
System.out.println("asyc");
util.evictCache();
}
@CacheEvict(value= "pi", allEntries = true)
public void evictCache() {
System.out.println("cacheevict");
}
public void methodTwo() {
util.evictCache();
System.out.println(this.getClass());
}
}
@SpringBootApplication @Configuration @EnableCaching public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(DemoApplication.class);
Util util = context.getBean(Util.class);
util.calPi(2);
util.calPi(2);
util.methodOne();
util.calPi(2);
util.evictCache();
util.calPi(2);
util.methodTwo();
util.calPi(2);
} }
Output: POST construct class com.amit.test.demo.Util$$SpringCGLIB$$0 inp 2 asyc cacheevict inp 2 cacheevict inp 2 cacheevict class com.amit.test.demo.Util inp 2
Here we get "inp 2" printed 4 times which is expected