Search code examples
javaspringspring-aopspring-cacheresthub

@Cacheable via Spring aop - how generate unique cache key


I would like to enable caching on several services that extends the same AbstractService for findById(Long id) method.

So in my applicationContext i wrote :

<!-- cache definitions -->
    <cache:advice id="cacheAdvice" cache-manager="cacheManager">
        <cache:caching cache="refs">
              <cache:cacheable method="findById" key="#root.targetClass + #id"/>
         </cache:caching>
    </cache:advice>

    <aop:config>
        <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* x.y.*.service.reference.*.*(..))"/>
    </aop:config>

The problem is i would like to generate a unique key for each service call on method findById because the ID can be the same ( and so have a class cast exception) :

java.lang.ClassCastException: x.y.model.RefSituation  cannot be cast to x.y.model.RefCivility

Unit test :

public class AbstractReferenceServiceTest extends AbstractBiTest {

    @Inject
    @Named("refSituationServiceClient")
    private RefSituationService refSituationService;

    @Inject
    @Named("refCivilityServiceClient")
    private RefCivilityService refCivilityService;

    @Test
    public void findById() {
        RefSituation situation = refSituationService.findById(1L);
        situation = refSituationService.findById(2L);
        situation = refSituationService.findById(1L);

        RefCivility refCivility = refCivilityService.findById(1L);
        refCivility = refCivilityService.findById(2L);
        refCivility = refCivilityService.findById(1L);
    }
}

Both services extends an AbstractReferenceService :

public interface RefSituationService extends AbstractReferenceService<RefSituation> {}
public interface RefCivilityService extends AbstractReferenceService<RefCivility> {}

And AbstractReferenceService extends A crudService provided by a framework called RestHub (https://github.com/resthub/resthub-spring-stack/blob/master/resthub-common/src/main/java/org/resthub/common/service/CrudService.java)

But with the configuration above i have an error :

org.springframework.expression.spel.SpelEvaluationException: EL1030E:(pos 0): The operator 'ADD' is not supported between objects of type 'java.lang.Class' and 'null'
    at org.springframework.expression.spel.ExpressionState.operate(ExpressionState.java:198)
    at org.springframework.expression.spel.ast.OpPlus.getValueInternal(OpPlus.java:97)
    at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:93)
    at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:89)
    at org.springframework.cache.interceptor.ExpressionEvaluator.key(ExpressionEvaluator.java:80)
    at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.generateKey(CacheAspectSupport.java:464)
    at org.springframework.cache.interceptor.CacheAspectSupport.inspectCacheables(CacheAspectSupport.java:291)
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:198)
    at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:66)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
    at com.sun.proxy.$Proxy175.findById(Unknown Source)

Thanks in advance for your help.


Solution

  • The problem is #root.targetClass.name is always "CrudService", to resolve the problem you have to :

    1- Implement your own CacheKeyGenerator :

    ApplicationContext.xml :

        <bean id="refCacheKeyGenerator" class="x.y.cache.RefCacheKeyGenerator" />
    
    <!-- cache definitions -->
        <cache:advice id="cacheAdvice" key-generator="refCacheKeyGenerator" cache-manager="cacheManager">
            <cache:caching cache="refs">
                  <cache:cacheable method="findById"/>
             </cache:caching>
        </cache:advice>
    
        <aop:config>
            <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* x.y.*.service.reference.*.*(..))"/>
        </aop:config>
    

    Java :

    public class RefCacheKeyGenerator implements org.springframework.cache.interceptor.KeyGenerator {
    
        @Override
        public Object generate(Object target, Method method, Object... params) {
            final List<Object> key = new ArrayList<>();
    
            key.add(method.getDeclaringClass().getName());      
            key.add(method.getName());
    
            List<Class<?>> clazz = ClassUtils.getAllInterfaces(target.getClass());
            if(CollectionUtils.isNotEmpty(clazz)){
                for(Class<?> sClass : clazz){
                    if(AbstractReferenceService.class.isAssignableFrom(sClass)){
                        if(!AbstractReferenceService.class.equals(sClass)){
                         key.add(sClass.getName());
                        }
                    }
                }
            }
            for (final Object o : params) {
                key.add(o);
            }
    
            return key;
        }
    
    }
    

    Test :

    public class RefCacheTest extends AbstractTest {
    
        @Autowired
        private RefSituationService refSituationService;
    
        @Autowired
        private RefCivilityService refCivilityService;
    
        @Autowired
        private CacheManager cacheManager;
    
    
        @Test
        public void findById() {
    
            Cache refCache = cacheManager.getCache(MyCache.REFS);
            refCache.setStatisticsEnabled(true);
    
            assertThat(refSituationService.findById(1L)).isInstanceOf(RefSituation.class);
            assertThat(refSituationService.findById(1L)).isInstanceOf(RefSituation.class);
            assertThat(refSituationService.findById(2L)).isInstanceOf(RefSituation.class);
    
            assertThat(refCivilityService.findById(1L)).isInstanceOf(RefCivility.class);
            assertThat(refCivilityService.findById(1L)).isInstanceOf(RefCivility.class);
            assertThat(refCivilityService.findById(2L)).isInstanceOf(RefCivility.class);
    
            System.out.println(refCache.getName() +" - "+ refCache.getStatistics().toString()); 
    
            assertThat(refCache.getStatistics().getCacheHits()).isEqualTo(2);
            assertThat(refCache.getSize()).isEqualTo(4);
        }