背景

使用ehcache实现@Cacheable时,默认的缓存键(key)是采用对应方法的参数值的组合,这样,如果程序广泛使用ehcache做缓存很容易造成key的冲突,导致缓存信息错乱。

默认ehcache有提供三种KeyGenerator

  1. DefaultKeyGenerator
  2. KeyGeneratorAdapter
  3. SimpleKeyGenerator

但是这三种都相对简单,下面就介绍一下怎么自定义KeyGenerator,并提供一个相对通用的方法。

自定义Cache KeyGenerator

代码方式


import java.lang.reflect.Method;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

@Configuration
@EnableCaching
@ComponentScan({ "com.demo.*" })
public class AppConfig implements CachingConfigurer {
@Override
@Bean
public CacheManager cacheManager() {
return new EhCacheCacheManager(ehCacheCacheManager().getObject());
}

@Bean
public EhCacheManagerFactoryBean ehCacheCacheManager() {
EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
cmfb.setConfigLocation(new ClassPathResource("main/resource/ehcache.xml"));
cmfb.setShared(true);
return cmfb;
}

@Override
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName());
sb.append(":");
sb.append(method.getName());
sb.append(":");
for (Object param : params) {
sb.append("|");
sb.append(param.toString());
}
return sb.toString();
}
};
}

@Override
public CacheResolver cacheResolver() {
// TODO Auto-generated method stub
return null;
}

@Override
public CacheErrorHandler errorHandler() {
// TODO Auto-generated method stub
return null;
}
}

配置方式

定义单独的Key Generator Class

import java.lang.reflect.Method;

import org.springframework.cache.interceptor.KeyGenerator;

public class EnhancedDefaultKeyGenerator implements KeyGenerator {

@Override
public Object generate(Object o, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName());
sb.append(":");
sb.append(method.getName());
sb.append(":");
for (Object param : params) {
sb.append("|");
sb.append(param.toString());
}
return sb.toString();
}
}

对应spring的完整配置文件如下:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">


<context:component-scan base-package="com.crunchify.controller" />

<mvc:annotation-driven></mvc:annotation-driven>
<cache:annotation-driven cache-manager="ehcacheCacheManager" />
<cache:annotation-driven key-generator="enhancedDefaultKeyGenerator" />
<bean id="enhancedDefaultKeyGenerator"
class="com.crunchify.controller.EnhancedDefaultKeyGenerator" />

<bean id="ehcacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">

<property name="configLocation" value="classpath:main/resource/ehcache.xml" />
</bean>
<bean id="ehcacheCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"
p:cacheManager-ref="ehcacheManager" />

<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">

<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />

<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>

其中添加了对应cache的一些支持,代码:

xmlns:cache="http://www.springframework.org/schema/cache" 
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="

http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd


<cache:annotation-driven cache-manager="ehcacheCacheManager" />
<cache:annotation-driven key-generator="enhancedDefaultKeyGenerator" />
<bean id="enhancedDefaultKeyGenerator"
class="com.crunchify.controller.EnhancedDefaultKeyGenerator" />

<bean id="ehcacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">

<property name="configLocation" value="classpath:main/resource/ehcache.xml" />
</bean>
<bean id="ehcacheCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"
p:cacheManager-ref="ehcacheManager" />

这里定义的key的结构为:类名:方法名:(参数值以|分割), 示例如下:

实现代码:

@Cacheable(value = "cache1")
public Movie findByDirector(String name) {
	//...
	return null;
}

obj.findByDirector("dummy")

这是对应的key为:
com.demo.test.MovieDaoImpl:findByDirector:|dummy

查看当前缓存里的内容:


import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.statistics.StatisticsGateway;

...

String[] cacheNames = CacheManager.getInstance().getCacheNames();

for (String cacheName : cacheNames) {
System.out.println("## CacheName: " + cacheName);
Ehcache cache = CacheManager.getInstance().getCache(cacheName);
System.out.println("cache records: " + cache.getSize());
StatisticsGateway st = cache.getStatistics();
System.out.println("heap size: " + st.getLocalHeapSizeInBytes() + " bytes");
System.out.println("disk size: " + st.getLocalDiskSizeInBytes() + " bytes");

for (Object key : cache.getKeys()) {
Element element = cache.get(key);

System.out.println(String.format("- key: %s, value: %s, hit: %d, created: %d, expiration: %d", key,
element.getObjectValue(), element.getHitCount(), element.getCreationTime(),
element.getExpirationTime()));
}
}

这样,对应ehcache里的内容就被打印出来了,如下:

## CacheName: cache2
cache records: 0
heap size: 0 bytes
disk size: 0 bytes
## CacheName: cache1
heap size: 760 bytes
disk size: 803 bytes
- key: com.demo.test.MovieDaoImpl:findByDirector:|dummy2, value: com.demo.test.Movie@71ba6d4e, hit: 1, created: 1452027528985, expiration: 1452027829081
- key: com.demo.test.MovieDaoImpl:findByDirector:|dummy, value: com.demo.test.Movie@55a147cc, hit: 2, created: 1452027526982, expiration: 1452027829081

参考

对应的ehcache.xml文件

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="false"
monitoring="autodetect" dynamicConfig="true">


<diskStore path="java.io.tmpdir" />

<cache name="cache1" maxEntriesLocalHeap="10000"
maxEntriesLocalDisk="1000" eternal="false" diskSpoolBufferSizeMB="20"
timeToIdleSeconds="300" timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU" transactionalMode="off">

<persistence strategy="localTempSwap" />
</cache>

<cache name="cache2" maxEntriesLocalHeap="10000"
maxEntriesLocalDisk="1000" eternal="false" diskSpoolBufferSizeMB="20"
timeToIdleSeconds="300" timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU" transactionalMode="off">

<persistence strategy="localTempSwap" />
</cache>
</ehcache>

可以借鉴的一些KeyGenerator

参考文章