一、问题现象
在我们的一个项目中通过Spring注解的方式来使用Ehcache,其中使用到的一个方法的返回值是一个List,测试发现,每次使用该方法得到的结果列表数据都有变化(原始数据没有变化,调用的参数没有变化,缓存也没有过期)。
二、问题原因的查找
我们项目中该方法的一个作用是修改列表数据中的对象的一个属性,因此推测是因为从Ehcache缓存中取出的是原始数据的引用,在方法中修改了引用的具体数据导致缓存原始的值被修改了。于是单步调试,发现在第二次从缓存中取出的数据果然是第一次修改后的数据,于是原因定位到。
三、解决方法
-
在该方法中,修改数据时不要修改原始数据,首先拷贝出一份数据,在拷贝出来的数据中修改,这样避免了修改原始数据。但这种方法切入的原来的代码,而且如果有其他方法有使用缓存的话也要修改,修改繁琐,因此不推荐。
-
Ehcache提供copyOnRead="true" copyOnWrite="true"的配置属性,作用是读取或写入数据时不使用原始数据,而使用拷贝数据。但使用该项配置时同时需要配置copyStrategy class属性,作用是提供copy策略。
<cache name="bannerCache" eternal="false" maxElementsInMemory="50"
overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="300"
timeToLiveSeconds="300" memoryStoreEvictionPolicy="LFU" copyOnRead="true" copyOnWrite="true">
<copyStrategy class="com.netease.silver.service.ehcache.EhcacheCopyStrategy" />
</cache>
参考下面博客实现了copy策略类
public class EhcacheCopyStrategy implements ReadWriteCopyStrategy<Element> {
@Override
public Element copyForWrite(Element value) {
if(value != null){
Object temp=(Serializable)value.getObjectValue();
return new Element(value.getObjectKey(),temp);
}
return value;
}
@Override
public Element copyForRead(Element storedValue) {
if(storedValue != null){
Object temp=(Serializable)storedValue.getObjectValue();
return new Element(storedValue.getObjectKey(),storedValue);
}
return storedValue;
}
}
于是再次测试,发现没有生效,取出的数据还是上次修改的数据。于是再次单步调试查找原因,最后定位在copy策略类的实现逻辑中,由于使用Ehcache缓存的方法返回的是列表数据,但是在copy策略类中实现的拷贝是浅拷贝,返回的数据还是原始数据的引用,因此没有生效,于是修改copy策略类,使用深度copy方法:
public class EhcacheCopyStrategy implements ReadWriteCopyStrategy<Element> {
@Override
public Element copyForWrite(Element value) {
if(value != null){
Object temp=(Serializable)value.getObjectValue();
try {
return new Element(value.getObjectKey(),deepCopy(temp));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return value;
}
@Override
public Element copyForRead(Element storedValue) {
if(storedValue != null){
Object temp=(Serializable)storedValue.getObjectValue();
try {
return new Element(storedValue.getObjectKey(),deepCopy(temp));
} catch (ClassNotFoundException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return storedValue;
}
private Object deepCopy(Object src) throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(src);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
return in.readObject();
}
}
再次测试发现问题解决!