i\'m working around spring 3.1 annotation cache with ehcache as a cache implement.
a method with return value like this
@Cacheable(\"cache\")
public
I had the same problem with the spring cache. I didn't want to receive the same java objects from the cache.
In my case i want to cache big java objects with many fields and so on. So it is very painful to copy all the data classes with deep copy. I read the article about copy java objects with serialization.
http://www.javaworld.com/article/2077578/learn-java/java-tip-76--an-alternative-to-the-deep-copy-technique.html
This brought me to the idea to cache only the serialized data. Every time a object is read from the cache it is deserialized.
For the serialization i used apache commons helper methods
@Override
public SerializedQuestions readUserQuestion(UID questionId, Locale locale) {
byte[] serializedData = readCachedUserQuestion(questionId, locale);
Object deserializedobject = org.apache.commons.lang.SerializationUtils.deserialize(serializedData);
return (SerializedQuestions) deserialize;
}
@Override
@Cacheable(value = SpringCacheKeys.USER_QUESTION_CACHE)
public byte[] readCachedUserQuestion(UID questionId, Locale locale) {
//read object from db
SerializedQuestions questions = new SerializedQuestions()
return org.apache.commons.lang.SerializationUtils.serialize(questions);
}
It depends on the spring configuration if the call to readCachedUserQuestion could be in the same class or not. Per default only extern calls to a method are cached.
I've found a little dirty solution that worked for me. by creating another class that it contains the same proprieties of The returned Object and then I've mapped the returned Object to my new Object using ModelMapper. for example i have a class MyObject:
public class MyObject {
private Long id;
private Long label;
//getters and setters
}
the new Created class:
public class MyObjectCopy {
private Long id;
private Long label;
//getters and setters
}
and a cachable method that returns MyObject:
@Cacheable("cache")
public MyObject getMyObject();
and to prevent cache to be modified : i must map that object to my classCopy then i work on it:
MyObjectCopy myObjectCopy = modelMapper.map(myobject, MyObjectCopy.class);
dont forget to create a copy class for the nested objects;
You could write your own aspect that always creates a copy of the returned value, which would make you independent of some Ehcache settings.
At first, a marker annotation like @CopyReturnValue
would be nice for expressing the pointcut:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CopyReturnValue {
}
Now, the aspect can use this annotation for the pointcut expression:
@Aspect
@Component
public class CopyReturnValueAspect {
@Around("@annotation(CopyReturnValue)")
public Object doCopyReturnValue(ProceedingJoinPoint pjp) throws Throwable {
Object retVal = pjp.proceed();
Object copy = BeanUtils.cloneBean(retVal); // create a copy in some way
return copy;
}
}
Finally, add the annotation to your method:
@CopyReturnValue
@Cacheable("cache")
public MyObject getObj(Object param);
For the CopyReturnValueAspect
I use BeanUtils to create a copy of the returned value - just as an example. For further information on that topic, you might want to look at How to copy properties from one Java bean to another?
Oh, don't forget to enable @AspectJ support in you Spring configuration if you haven't already:
<aop:aspectj-autoproxy />