Explain Codes LogoExplain Codes Logo

Spring Cache @Cacheable - not working while calling from another method of the same bean

java
spring-cache
aop-proxies
cache-configuration
Alex KataevbyAlex Kataev·Mar 7, 2025
TLDR

@Cacheable doesn't work with internal method calls within the same bean. It only triggers when the method is called through the bean's proxy. Utilize self-injection to nab this proxy within the same class, or divide functionality over distinct beans.

Here is a cheeky example using self-injection and field injection:

@Service public class MyService { // Selfie time (i.e., self-injection) @Autowired private MyService proxy; public void callCachedMethod() { proxy.cachedOperation(); // Time to call up the cache, fingers crossed 🤞 } @Cacheable(cacheNames = "myCache") public String cachedOperation() { return "cache this if you can!"; } }

Constructor injection is a no-go. It presents risks of self-references and circular dependencies. So, stick with field or setter injection.

Demystifying how Spring Cache works with internal calls

Spring leverages dynamic proxies to intercept @Cacheable method calls. But here's the secret sauce: these proxies only work when calls originate from outside the bean. Yes, no exceptions! This behavior resembles how you can't invoke a private method from other classes. Instead, you need to communicate through the bean's proxy.

Proxies and their preferences

Spring's AOP proxies choose between JDK dynamic proxies and CGLIB proxies depending on circumstances and your config. Here's a pro tip: JDK proxies cache calls made on the interface methods only, not the class methods. CGLIB proxies work with class method calls too. However, in both cases, internal calls within the same class are left out. They never meet the proxy and so, @Cacheable isn't triggered.

Recipes for caching with internal method calls

To overcome this limitation, you can set proxyTargetClass to true in your cache configuration or self-inject the bean to bring the proxy in the picture.

Beware of potential circular dependencies while autowiring ApplicationContext with care. If none of these tricks tickle your fancy, how about partitioning your cached methods into a different bean altogether?

Extra measures: AspectJ and cache debugging

For advanced caching needs involving heavy intra-class calls, strategist suggest turning to AspectJ. AspectJ carries out compile-time weaving, rather than runtime proxies, bypassing the "proxy-only" hurdle.

To understand the life cycle of your cache, debugging comes quite handy. Print or log every success and failure case to keep track of cache hits and misses at runtime.

Spring Cache optimization: Do's and Don'ts

Caching can be a secret weapon to boost your application's performance, but only when done judiciously and strategically. Here are some time-tested tips to extract the max out of your cache setup.

Cache configuration best practices

Maintain a uniform caching pattern using @CacheConfig to the class level. This reduces redundancy and improves readability and manageability of cache configuration.

Advanced caching techniques: self-reference, capacity planning, and eviction policies

To enrich the functionality, you can configure the eviction policies with @CacheEvict to clear old cache data and optimize memory usage. You could also integrate a capacity planning process to scale your caching needs accurately over time.

In special circumstances where you absolutely need to employ ApplicationContext, you can do it with the following setup:

@Autowired private ApplicationContext applicationContext; public String invokeCacheable() { MyService beanProxy = applicationContext.getBean(MyService.class); return beanProxy.invokeCacheable(); }

Be wary of ApplicationContext though, it can create unwanted dependencies. It's like calling your boss at 2 a.m., only for a real emergency!