I want to cache master data to Redis.
So, I had written these codes.
@Configuration
@EnableCaching
public class AppConfig extends CachingConfigurerSupport {
@Bean
@Autowired
public CacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
Map<String, Long> expires = new HashMap<>();
expires.put("cache.day", new Long(24 * 60 * 60));
cacheManager.setExpires(expires);
return cacheManager;
}
}
And
package com.taisho.artifacts.repository.impl;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
@Repository
public class TestRepository {
@Cacheable(value = "cache.day", key = "'cache.test'")
public List<String> getTest() {
List<String> list = new ArrayList<>();
list.add("test");
list.add("sample");
return list;
}
public void printTest() {
System.out.println(getTest());
}
}
And ymlfile
spring:
redis:
host: 127.0.0.1
port: 26379
But, Cache not working...
Whenever I call printTest method, "getTest" method will execute. Redis has no data... What is a problem in my code?
SpringBoot version is 1.4.0
dependencies are
compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-data-redis:${springBootVersion}")
compile("org.springframework.boot:spring-boot-autoconfigure:${springBootVersion}")
As the name implies, @Cacheable is used to demarcate methods that are cacheable - that is, methods for whom the result is stored into the cache so on subsequent invocations (with the same arguments), the value in the cache is returned without having to actually execute the method.
Spring AOP is proxy-based, so when you calling the getTest()
method from the printTest()
method, the getTest()
method would be called on this
reference not the proxied version which is capable of performing caching operations. Usually this is a Design Smell and you are better off reconsidering your current design. But as a workaround, you can use the AopContext
:
public void printTest() {
System.out.println(((TestRepository) AopContext.currentProxy()).getTest());
}
Suppose you have a client code that has access to the TestRepository
through Dependency Injection:
@Component
class SomeUnfortunateClient {
// I know field injection is evil!
@Autowired TestRepository testRepository;
void youAreGoingToBeSurprised() {
testRepository.printTest();
}
}
The TestRepository
is a Spring managed repository and in order to add the additional functionalities to the TestRepository
, e.g. Caching, Spring will create a Proxy for it. This means that method calls on the testRepository
object reference will be calls on the proxy, and as such the proxy will be able to delegate to all of the interceptors (advice) that are relevant to that particular method call. In your case, those advises would check if the cache entry exists or not.
However, once the call has finally reached the target object, the TestRepository
reference in this case, any method calls that it may make on itself, such as System.out.println(getTest());
, are going to be invoked against the this
reference, and not the proxy. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.
As the Spring Documentation states:
Okay, so what is to be done about this? The best approach (the term best is used loosely here) is to refactor your code such that the self-invocation does not happen. For sure, this does entail some work on your part, but it is the best, least-invasive approach. The next approach is absolutely horrendous, and I am almost reticent to point it out precisely because it is so horrendous. You can (choke!) totally tie the logic within your class to Spring AOP by doing this:
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
This totally couples your code to Spring AOP, and it makes the class itself aware of the fact that it is being used in an AOP context, which flies in the face of AOP. It also requires some additional configuration when the proxy is being created.
This answer is heavily based on Spring Documentation, so for (even!) more detailed discussion, you should definitely check out the Understanding AOP proxies section in Spring Documentation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With