@Cacheable через Spring aop - как сгенерировать уникальный ключ кэша

Я хотел бы включить кеширование для нескольких сервисов, которые расширяют один и тот же метод AbstractService для findById(Long id).

Итак, в моем applicationContext я написал:

<!-- 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>

Проблема в том, что я хотел бы сгенерировать уникальный ключ для каждого вызова службы в методе findById, потому что идентификатор может быть одинаковым (и поэтому иметь исключение приведения класса):

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

Модульный тест :

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);
    }
}

Обе службы расширяют AbstractReferenceService :

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

А AbstractReferenceService расширяет crudService, предоставляемый фреймворком RestHub (https://github.com/resthub/resthub-spring-stack/blob/master/resthub-common/src/main/java/org/resthub)./common/service/CrudService.java)

Но с приведенной выше конфигурацией у меня есть ошибка:

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)

Заранее спасибо за помощь.


person Denis Cucchietti    schedule 17.02.2014    source источник
comment
Неясно, вызывает ли проблема интерфейс RefSituation или интерфейс RefCivility. Можете ли вы опубликовать код для обоих? Я бы также предложил разделить тест на два отдельных теста, чтобы изолировать проблему.   -  person Peter Bratton    schedule 17.02.2014
comment
Привет, Питер, я обновил исходный пост, спасибо   -  person Denis Cucchietti    schedule 17.02.2014


Ответы (4)


Проблема в том, что #root.targetClass.name всегда "CrudService", чтобы решить проблему, вам нужно:

1- Реализуйте свой собственный 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>

Ява :

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;
    }

}

Тест :

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);
    }
person Denis Cucchietti    schedule 18.02.2014

Скорее всего, здесь происходит проблема с оператором + и типами, которые могут быть или не быть Strings. Предполагая, что вы хотите конкатенацию строк (длинная арифметика приведет к конфликтам, которых, я думаю, вы пытаетесь избежать), принуждение ваших ключевых аргументов к Strings может решить проблему. Ала:

<!-- cache definitions -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
    <cache:caching cache="refs">
          <cache:cacheable method="findById" key="#root.targetClass.name + #id.toString()"/>
     </cache:caching>
</cache:advice>
person Peter Bratton    schedule 17.02.2014
comment
Та же проблема с приведенным выше кодом: org.springframework.expression.spel.SpelEvaluationException: EL1011E:(pos 29): вызов метода: попытка вызвать метод toString() для объекта нулевого контекста - person Denis Cucchietti; 18.02.2014

Я попробовал сейчас, и это работает только со следующей конфигурацией, без дополнительной пользовательской реализации генератора ключей:

@Cacheable(value = "lookups", key="T(org.springframework.cache.interceptor.SimpleKeyGenerator).generateKey(#root.target.class, #root.args)")

Основная идея здесь включает конкретный класс объекта как одну из ключевых частей.

person Hubbitus    schedule 21.12.2015

Вот более простой способ создать уникальный ключ:

@Cacheable(value = "all-coupons", cacheManager="cacheManagerCompany" , key="#root.method + #this.toString()")
@Override
public List<Coupon> getAllCoupons() {

    return compDBDAO.getCoupons();
}

Решение этой проблемы заключается в использовании key="#root.method + #this.toString().

person Ron Shoshani    schedule 13.07.2018