Spring Ehcache3 вызывает исключение с типом ключа и типом значения
Я пытаюсь использовать ehcache3
на проекте с пружиной 4.3. Я настроил кеш-менеджер:
<cache:annotation-driven />
<bean id="cacheManager" class="org.springframework.cache.jcache.JCacheCacheManager">
<property name="cacheManager">
<bean class="org.springframework.cache.jcache.JCacheManagerFactoryBean">
<property name="cacheManagerUri" value="classpath:ehcache.xml"/>
</bean>
</property>
</bean>
И ehcache.xml:
<config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
xsi:schemaLocation="
http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd" >
<service>
<jsr107:defaults enable-statistics="true" enable-management="true"/>
</service>
<cache alias="customerSettings">
<key-type>java.lang.Long</key-type>
<expiry>
<none/>
</expiry>
<resources>
<heap>500</heap>
</resources>
</cache>
</config>
Но когда я развертываю проект, у меня есть исключение:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cacheManager' defined in ServletContext resource [/WEB-INF/spring/root-context.xml]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Cache [customerSettings] specifies key/value types. Use getCache(String, Class, Class)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1628)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
... 100 more
Caused by: java.lang.IllegalArgumentException: Cache [customerSettings] specifies key/value types. Use getCache(String, Class, Class)
at org.ehcache.jsr107.Eh107CacheManager.getCache(Eh107CacheManager.java:297)
at org.springframework.cache.jcache.JCacheCacheManager.loadCaches(JCacheCacheManager.java:105)
at org.springframework.cache.support.AbstractCacheManager.initializeCaches(AbstractCacheManager.java:61)
at org.springframework.cache.support.AbstractCacheManager.afterPropertiesSet(AbstractCacheManager.java:50)
at org.springframework.cache.jcache.JCacheCacheManager.afterPropertiesSet(JCacheCacheManager.java:97)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
... 107 more
Если я удалю:
<key-type>java.lang.Long</key-type>
Это нормально работает, но тип ключа кеша - Object. Что нужно сделать, чтобы я мог использовать собственный тип ключа и тип значения?
3 ответа
Spring кеш не типизирован, поэтому он не использует типизированный API Jcache (javax.cache / JSR-107 Cache API)
Теперь, так как вы указали типы в своем ehcache.xml, Ehcache отказался позволить Spring использовать нетипизированную подпись getCache()
Когда вы думаете об этом, если вы разрешаете Spring использовать Ehcache (например, через @CacheResult и другие аннотации JCache), вы должны позволить ему выбирать, какие типы ключей и значений - вам больше не нужно указывать типы.
Как вы можете видеть из источников org.springframework.cache.jcache.JCacheCacheManager Spring не понимает, что он должен использовать метод getCache(String, Class, Class) вместо простого getCache(String). Точнее, этот класс ничего не знает о getCache(String, Class, Class).
Итак, у вас есть три способа:
Ничего не делайте, поскольку во время операций get и put кеш использует equals() и может быть методами hashCode() из реального класса вашего ключа. Только явный дискомфорт заключается в явном приведении типов, если вы используете прямой доступ к кешу вместо декларативного доступа через аннотации.
Расширяет этот класс и изучает его, чтобы понять эти функции конфигурации кэша.
Посмотрите на другой CacheManager, который может знать эти настройки.
Хорошо, вы должны немного взломать:
написать кастом CacheManager
и использовать его в вашей конфигурации xml:
<bean id="cacheManager" class="your.path.MyCustomLongObjectJCacheManager">
<property name="cacheManager">
<bean class="org.springframework.cache.jcache.JCacheManagerFactoryBean">
<property name="cacheManagerUri" value="classpath:ehcache.xml"/>
</bean>
</property>
</bean>
Вот некоторый (псевдо) код:
public class MyCustomLongObjectJCacheManager extends JCacheCacheManager{
@Override
protected Collection<Cache> loadCaches() {
javax.cache.CacheManager cacheManager = getCacheManager();
Collection<Cache> caches = new LinkedHashSet<Cache>();
for (String cacheName : getCacheManager().getCacheNames()) {
if("customerSettings".equals(cacheName)){ // or manager instance of Eh107CacheManager...
javax.cache.Cache<Long, Object> jcache = cacheManager.getCache(cacheName, Long.class, Object.class);
caches.add(new MyCustomAdaptingCache(jcache, isAllowNullValues()));
} else {
javax.cache.Cache<Object, Object> jcache = cacheManager.getCache(cacheName);
caches.add(new JCacheCache(jcache, isAllowNullValues()));
}
}
return caches;
}
@Override
protected Cache getMissingCache(String cacheName) {
// Check the JCache cache again (in case the cache was added at runtime)
javax.cache.CacheManager cacheManager = getCacheManager();
if("customerSettings".equals(cacheName)){
javax.cache.Cache<Long, Object> jcache = cacheManager.getCache(cacheName, Long.class, Object.class);
return new MyCustomAdaptingCache(jcache, isAllowNullValues());
}
javax.cache.Cache<Object, Object> jcache = getCacheManager().getCache(cacheName);
if (jcache != null) {
return new JCacheCache(jcache, isAllowNullValues());
}
return null;
}
}
public static class MyCustomAdaptingCache extends AbstractValueAdaptingCache {
private final javax.cache.Cache<Long, Object> cache;
public MyCustomAdaptingCache(javax.cache.Cache<Long, Object> jcache) {
this(jcache, true);
}
public MyCustomAdaptingCache(javax.cache.Cache<Long, Object> jcache, boolean allowNullValues) {
super(allowNullValues);
Assert.notNull(jcache, "Cache must not be null");
this.cache = jcache;
}
@Override
public final String getName() {
return this.cache.getName();
}
@Override
public final javax.cache.Cache<Long, Object> getNativeCache() {
return this.cache;
}
@Override
protected Object lookup(Object key) {
return this.cache.get((Long)key);
}
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
try {
return this.cache.invoke((Long)key, new ValueLoaderEntryProcessor<T>(), valueLoader);
}
catch (EntryProcessorException ex) {
throw new ValueRetrievalException(key, valueLoader, ex.getCause());
}
}
@Override
public void put(Object key, Object value) {
this.cache.put((Long)key, toStoreValue(value));
}
@Override
public ValueWrapper putIfAbsent(Object key, Object value) {
boolean set = this.cache.putIfAbsent((Long)key, toStoreValue(value));
return (set ? null : get(key));
}
@Override
public void evict(Object key) {
this.cache.remove((Long)key);
}
@Override
public void clear() {
this.cache.removeAll();
}
private class ValueLoaderEntryProcessor<T> implements EntryProcessor<Long, Object, T> {
@SuppressWarnings("unchecked")
@Override
public T process(MutableEntry<Long, Object> entry, Object... arguments)
throws EntryProcessorException {
Callable<T> valueLoader = (Callable<T>) arguments[0];
if (entry.exists()) {
return (T) fromStoreValue(entry.getValue());
}
else {
T value;
try {
value = valueLoader.call();
}
catch (Exception ex) {
throw new EntryProcessorException("Value loader '" + valueLoader + "' failed " +
"to compute value for key '" + entry.getKey() + "'", ex);
}
entry.setValue(toStoreValue(value));
return value;
}
}
}
}
удачи.