Spring #Cacheable doesn't cache public methods - java

I have following method;
#Cacheable(value = "psgSiteToMap", key = "'P2M_'.concat(#siteName)")
public Map getSiteDetail(String siteName) {
Map map = new HashMap();
.....
//construct map variable here
.......
return map;
}
While project startup, cannot autowire class this method belongs to. If i change above method as following;
#Cacheable(value = "psgSiteToMap", key = "'P2M_'.concat(#siteName)")
private Map getSiteDetail(String siteName) {
Map map = new HashMap();
.....
//construct map variable here
................
return map;
}
public Map getSiteDetailPublic(String siteName) {
return this.getSiteDetail(siteName);
}
it works. Is there any restriction on #Cacheable annotation for public methods?
Thanks in advance

Spring AOP works only on public methods by default. You'd need AspectJ and load time or compile time weaving to make it work on private methods.
So it works in your case means that when you move the #Cacheable to the private method the proxy is not created at all and that works is autowireing, but not caching.
You probably have not set proxy-target-class property in your XML configuration or its equivalent annotation attribute. Can you please add the Spring configuration you're using and the class definition line. I'm interested if it implements any interfaces? Than I'll expand my answer with more details.

Related

Spring Boot 2.5 Upgrade - Objects in SpringBootTest executed in Spock appear to be overwritten

I am having an issue running some integration tests that are run using the Spock framework after upgrading my application.
I've got a configuration that returns a "Mock" object that is an extension of a class. The class that is extended contains several members of type HashMap that are initialized on declaration.
If I manually instantiate that object before my #Bean method returns it, it has all of the attributes instantiated. In this case, it has several members that are initialized by default to LinkedHashMaps. However, the object that is returned by the #Bean method has those attributes assigned to null. So somehow the members are set to null, or the object is replaced altogether.
Note that the accessors for those maps are generated by lombok. I see in the debugger that the Bean for that class has those accessors defined in its meta information. The attributes themselves are null.
My upgrade is from 2.1.9 to 2.5. The tests are written in Groovy and executed in Spock. The "Mock" subclass is written and instantiated in an #TestConfiguration annotated class, but its superclass is written in Java with the lombok annotations. This used to work in Spring 2.1.9 but is breaking in 2.5. I've started the application with debug=true but none of the output has provided any clues/leads.
Any ideas here would be helpful. Let me know if I can provide further information to assist your answer. Thanks in advance!
The Configuration Class:
#TestConfiguration
public class IntegrationTestConfiguration {
...
...
#Bean
public MyCache spendAndMetricsCache() {
CacheTestExtension myCache = new CacheTestExtension();
return new CacheTestExtension();
}
...
...
}
The super class:
public class MyCache implements Reloadable {
private static final int HOUR_AND_DAY_LOOKBACK_DAYS = 2;
private static final int WEEK_LOOKBACK_DAYS = 8;
#Builder.Default #Getter
private Map<Long, TObjectLongHashMap<String>> lifetimeMetrics = new ConcurrentHashMap<>();
#Builder.Default #Getter
private Map<Long, TObjectLongHashMap<String>> monthlyMetrics = new ConcurrentHashMap<>();
...
...
}
The Extended Test Class:
public class CacheTestExtension extends MyCache {
protected Set<Integer> twoDayQueryIntervalIds() {
return new HashSet<>();
}
protected Set<Integer> eightDayQueryIntervalIds() {
return new HashSet<>();
}
}
Note that in the debugger I stop after instantiating the CacheTestExtension the HashMaps assigned to the member variables are initialized to empty maps. After my context is wired together with Autowiring, the reference to the cache is to an instance of the CacheTestExtension, but the member variables are null.

Redis: Set different time to live for methods annotated with #Cacheable

I have a set of cached methods that look somewhat like this:
#Cacheable(value = "myCacheName", keyGenerator = "myKeyGenerator")
public Product getProduct(ProductRequest request) {
// ...
}
And I need to set different time to live (expiration interval) for objects returned by these methods.
Problem: According to the documentation, the offered way is using #RedisHash(timeToLive=…​) or #TimeToLive annotations on the return type of the methods. However, I don't want to pollute my domain classes with caching related logic. In addition, some of my methods return strings or objects of classes which I can not modify. I would prefer to implement it in a more configurable way. There is also a configuration property called spring.cache.redis.time-to-live, but it applies the same time-to-live in all places.
Question: Is there a way to specify time to live/expiration interval on the method level? Or generally, how to implement it in a more elegant way?
Hi if you want to use only Spring annotations one way to do this is the following.
#CacheConfig annotation allows you to define specific CacheManager to use further more the #Cacheable annotation also allows defining cacheManager
#CacheConfig(cacheNames="myCacheName",cacheManager="timeoutCacheManager")
class ProductReader {
#Cacheable(value = "myCacheName", keyGenerator = "myKeyGenerator")
public Product getProduct(ProductRequest request) {
// ...
}
}
#Bean
public CacheManager timeoutCacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setDefaultExpiration(mytimeToLive);
return cacheManager;
}
Here is also a fragment of a more extensive cache configuration that is resulting again in a CacheManager. This time it configures multiple regions:
#Bean (name="cacheManager")
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration conf_ready_info = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMillis(50000));
RedisCacheConfiguration conf_base_info = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMillis(60000));
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<String, RedisCacheConfiguration>();
cacheConfigurations.put("base_info", conf_base_info);
cacheConfigurations.put("ready_info", conf_ready_info);
return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory)
.withInitialCacheConfigurations(cacheConfigurations).build();
}
I took the last example from:
set expire key at specific time when using Spring caching with Redis
Using only #Cacheable(value = "myCacheName", keyGenerator = "timeoutCacheManager")

Injecting Map with Enum as value in Spring

I'm looking on to how I can inject a Map<Integer, CustomEnumType> in Spring, not using app-context.xml but using #Bean(name = "customMap") annotations. When I try to inject it by doing
#Inject
Map<Integer, CustomEnumType> customMap;
it complains because apparently it cannot find any injectable dependency of type CustomEnumType. However CustomEnumType is just an enumeration, not something that is supposed to be injected. I just want to use it as the value type of my map.
One solution is to create an injectable wrapper object that will contain the Map as a field but I'd like to avoid unnecessary clutter. It is also more clean and readable to see the type of Map being injected.
Don't try to inject Enums, instead, try to inject int values.
Enums are indeed classes, however, you can't create an instance/object of them. their constructor access modifier can be private only, that's another proof why you can't have instances of them.
With that being said, you can't have Beans of Enum, because there is no way to construct them.
The solution is to give each member in your enum, an int value, and just inject that int value.
For example:
public enum Color {
white(0),
black(1);
private int innerValue;
private Color(int innerValue) {
this.innerValue = innerValue;
}
public int getInnerValue() {
return innerValue;
}
}
Now, let's say I want to inject the value 1, which is black in my Enum. To another Class via its constructor. Then my constructor will look like this:
public Canvas(String id, int color) {
this.id = id;
this.color = Color.getColorByInt(color);
}
Now, let's say that the xml configuration file containing this:
<bean id="myCanvas" class="com.my.package.Canvas">
<constructor-arg name="id" value="9876543" />
<constructor-arg name="color" value="1" />
<!-- This is the black value for myCanvas bean -->
</bean>
I found the solution. Apparently #Inject and #Autowired fail to correctly find the type of #Bean method they need to use. However, using #Resource(name = "customMap") everything worked perfectly. There was no problem with the map creation even if the values were enumerations. The method used was:
#Bean(name = "customMap")
public Map<Integer, CustomEnumType> getCustomMap() {
Map<Integer, CustomEnumType> map = new HashMap<>();
map.put(1, CustomEnumType.type1);
map.put(2, CustomEnumType.type2);
//...
return map;
}
and injected using
#Resource(name = "customMap")
Map<Integer, CustomEnumType> customMap;
Note that CustomEnumType has no constructors defined and no values are assigned to the enumeration. In my case this was also impossible to do from the beginning since the CustomEnumType class is a dependency we cannot edit.

Can I use the #Resource annotation in a setter instead of a field?

I currently I have:
#Resource(name = "my.map")
private Map<String, String> externalMap;
Can I use the resource annotation in the setter instead with the same result?
Like this?
private Map<String, String> externalMap;
#Resource(name = "my.map")
void setExternalMap(Map<String,String> aMap) {
this.externalMap = aMap;
}
According to the JavaDoc for the Resource Annotation, the annotation may be applied to a class, or fields or methods. Requested resources will be injected into the method. An example of this usage exists here.

How to create a MAP that has an application scope? And Where to declare it?

I need to create a Map that has application scope. And so, if user1 add to this Map an object using method1 of class1, user2 would find the new objects using method2 of class2.
I know there is this annotation :
#ApplicationScoped
But, I don't know where my map should be declared or used, to make it have the same state at anytime and by anywhere in the application deployment time.
An example representing a class where this Map is declared and a method of another class using it, would be so helpful.
Declare a CDI bean that will provide this Map for its consumption:
#Named
#ApplicationScoped
public class ApplicationScopedBean {
private Map<KeyClass, ValueClass> map;
#PostConstruct
public void init() {
//initialize the map and its data here
map = new ConcurrentHashMap<>();
map.put(..., ...);
//...
}
//provide a getter for the map
public Map<KeyClass, ValueClass> getMap() {
return this.map;
}
}
Now, the bean can be injected in clients and can show the data in your view.
I'm not sure about your experience with Java language. But why don't you create a static variable in class?
So, for example:
class A {
public static Map<String, String> globalMap;
}
From class B you could access or set it anywhere(ofcourse you need to import the class A at the top):
class B {
public void doAnything(){
Map anyMap<String, String> anyMap = new HashMap<String, String>();
anyMap.put("anyString", "anyString");
A.globalMap = anyMap;
}
}

Categories

Resources