2

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 MyObject getObj(Object param);

i got a myobject return value for the first time,and it's editable. ehcache can do something for that by setting "copyOnRead" or "copyOnWrite". it will force serialize object on read/write. but at the first time spring will not get value from cache,it always return by method itself.

is there some way to get a readonly return value?

Foxswily
  • 53
  • 2
  • 7

3 Answers3

3

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 />
Community
  • 1
  • 1
jeha
  • 10,562
  • 5
  • 50
  • 69
  • actually it will work ,but it's a little heavyweight. and i have to remember the order of @CopyReturnValue and @Cacheable("cache"). – Foxswily Jun 14 '12 at 10:26
  • i don't know why spring doesn't get object from cache when method run successfully first(thers is no obj in cache and it will store obj into cache).it should store into cache and then take it out,in this way,copyOnWrite will work. – Foxswily Jun 14 '12 at 10:30
  • @Foxswily: I aggree with you, but the `EhCacheInterceptor` (lines 140-155) currently does not work that way. It returns the original. You already described what should be done instead to make it work the way you need it. It's just one line of code ... Maybe tell the guys from ehcache-spring-annotations or DIY ... – jeha Jun 14 '12 at 14:06
  • thank you @jeha ,i'll tell the guys from spring( spring 3.1 can do things like ehcache-spring-annotations ) – Foxswily Jun 15 '12 at 01:26
1

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.

Sam Hueppi
  • 49
  • 7
0

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;