首页发现篇,虽然跟我的不一样,但思路大致相同,大家可以参考下咯
http://mixer-b.iteye.com/blog/1563872
以前没具体做过缓存,于是上网搜了下资料,于是得知spring+ehcache做缓存一些考虑因素:
1、缓存的切面放在哪一层最合适(大部分情况是service,dao),其实应用在哪一层都有各自的用武之地,如:
一、放在service,是缓存整个经过业务处理后的一个结果,这样的做法大大减少了系统逻辑处理,但如果业务方法里面涉及到多表操作,则比较麻烦,因为要考虑缓存数据更新的问题。
二、放在dao,大多数都放在这层,也是推荐放在这层,因为基本不用人为的考虑缓存及时更新的问题造成业务方法返回的结果不一致。只缓存数据,不牵扯到业务逻辑
2、对于某些特殊情况下的方法,不需要缓存,或者不需要更新缓存的方法,通过参数排除
3、考虑需要缓存更新,所以需要两个拦截器去拦截不同的方法,做不同的处理
一、缓存拦截器,在方法调用之前拦截,如(find,query,select,get方法),经过一些逻辑处理,再判断返回缓存还是真实的数据
二、更新缓存拦截器,在方法调用之后,如(save,insert,update,delete方法)
于是马上动手:
缓存拦截器 MethodCacheInterceptor
package com.eclink.cache;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Logger;
import org.junit.Assert;
import org.springframework.beans.factory.InitializingBean;
import com.eclink.common.Common;
public class MethodCacheInterceptor implements MethodInterceptor,
InitializingBean {
private static final Logger log = Logger.getLogger(MethodCacheInterceptor.class);
private Cache cache;
private List<String> noCacheMethod;
public void setNoCacheMethod(List<String> noCacheMethod) {
this.noCacheMethod = noCacheMethod;
}
public void setCache(Cache cache) {
this.cache = cache;
}
/**
* 拦截service/dao中的方法,如果存在该结果,则返回cache中的值。<br>
* 否则,则从数据库中查询返回并放入cache中
* @throws Throwable
*/
public Object invoke(MethodInvocation invocation) throws Throwable {
String targetName = invocation.getThis().getClass().getName();
String methodName = invocation.getMethod().getName();
Object[] args = invocation.getArguments();
Object result = null;
// 排除不需要缓存的方法
boolean noCache = false;
if(!Common.isEmpty(noCacheMethod)){
String s = targetName +"."+methodName;
for (String method : noCacheMethod) {
if(s.indexOf(method) != -1){
noCache = true;
break;
}
}
}
if(noCache){
// 不需要缓存,直接调用方法返回值
return invocation.proceed();
}
else{
String cacheKey = getCacheKey(targetName, methodName, args);
log.debug("find object from "+cache.getName()+" : "+cacheKey);
Element e = cache.get(cacheKey);
if(e == null){
log.debug("can't find object in "+cache.getName()+", get method result and create the cache(key: "+cacheKey+").");
result = invocation.proceed();
if(result == null)return null;
if(!(result instanceof Serializable)){
// 如果返回结果不可序列化,则不缓存,直接返回
log.info(result + " is not Serializable,Can't use cache.");
return result;
}
e = new Element(cacheKey, result);
cache.put(e);
}
// 返回克隆的对象,这个看需求了,如果你想直接修改缓存里对象的值,就直接返回缓存里的对象
return cloneObject(e.getValue());
}
}
/**
* 克隆对象
* @param obj
* @return
*/
private Object cloneObject(Object obj){
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return ois.readObject();
}catch(NotSerializableException e1){
log.warn(obj+" can't be clone");
e1.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(ois != null)
ois.close();
if(bis != null)
bis.close();
if(oos != null)
oos.close();
if(bos != null)
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return obj;
}
/**
* 构建缓存的key。
* 获得cacheKey的方法. key为:包名.类名.方法名 如:com.eclink.service.buscert.BusCertService.getBusCertList
* @param targetName 类名
* @param methodName 方法名
* @param args 参数
* @return String
*/
private String getCacheKey(String targetName, String methodName, Object[] args){
StringBuilder sb = new StringBuilder();
sb.append(targetName).append(".").append(methodName);
if(!Common.isEmpty(args)){
for (Object arg : args) {
// 这里将参数克隆,防止直接修改传入的对象参数。
// TODO 只判断了简单的map,和object[]这种特殊情况,具体情况视项目情况再完善
Object o = cloneObject(arg);
if(o instanceof Map){
Map mapo = (Map)o;
Set<String> keys = mapo.keySet();
for (String key : keys) {
Object keyValue = mapo.get(key);
if(keyValue instanceof Object[]){
mapo.put(key, convertObjectArray((Object[])keyValue));
}
}
}
if (o instanceof Object[]) {
sb.append(".").append(convertObjectArray((Object[])o));
}else{
sb.append(".").append(o);
}
}
}
return sb.toString();
}
/**
* 将数组原始的toString()方法,转换成字符串
* @param objs
* @return
*/
public String convertObjectArray(Object[] objs){
StringBuilder sb = new StringBuilder();
sb.append("[");
for (Object obj : objs) {
sb.append(obj).append(",");
}
sb.append("]");
return sb.toString();
}
public void afterPropertiesSet() throws Exception {
Assert.assertNotNull("Need a cache,pleace use setCache(cache) create it.", cache);
}
}
更新缓存拦截器,MethodCacheAfterAdvice
package com.eclink.cache;
import java.lang.reflect.Method;
import java.util.List;
import net.sf.ehcache.Cache;
import org.apache.log4j.Logger;
import org.junit.Assert;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.beans.factory.InitializingBean;
import com.eclink.common.Common;
public class MethodCacheAfterAdvice implements AfterReturningAdvice,
InitializingBean {
private static final Logger log = Logger.getLogger(MethodCacheAfterAdvice.class);
private Cache cache;
private List<String> noFlushCacheMethod;
public void setNoFlushCacheMethod(List<String> noFlushCacheMethod) {
this.noFlushCacheMethod = noFlushCacheMethod;
}
public void setCache(Cache cache) {
this.cache = cache;
}
/**
* 刷新缓存(在目标方法执行之后,执行该方法)
*/
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
String className = target.getClass().getName();
String methodName = method.getName();
// 排除不需要刷新缓存的方法
boolean noFlushCache = false;
if(!Common.isEmpty(noFlushCacheMethod)){
String s = className +"."+methodName;
for (String m : noFlushCacheMethod) {
if(s.indexOf(m) != -1){
noFlushCache = true;
break;
}
}
}
if(!noFlushCache){
List<String> cacheKeys = cache.getKeys();
for (String cacheKey : cacheKeys) {
if(cacheKey.startsWith(className)){
cache.remove(cacheKey);
log.debug("remove cache "+cacheKey);
}
}
}
}
public void afterPropertiesSet() throws Exception {
Assert.assertNotNull("Need a cache,pleace use setCache(cache) create it.", cache);
}
}
spring 配置文件
<bean id="defaultCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation">
<value>classpath:ehcache.xml</value>
</property>
</bean>
<!-- defind cache factory and set cacheName -->
<bean id="ehCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager" ref="defaultCacheManager" />
<property name="cacheName">
<value>FDOL2_CACHE</value>
</property>
</bean>
<!-- find/create cache -->
<bean id="methodCacheInterceptor" class="com.eclink.cache.MethodCacheInterceptor">
<property name="cache" ref="ehCache" />
<property name="noCacheMethod">
<list>
<value>getNextSerialNo</value>
<value>getNextNo</value>
</list>
</property>
</bean>
<!-- flush cache -->
<bean id="methodCacheAfterAdvice" class="com.eclink.cache.MethodCacheAfterAdvice">
<property name="cache" ref="ehCache" />
<property name="noFlushCacheMethod">
<list>
<value>NoFlushCache</value>
</list>
</property>
</bean>
<bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="methodCacheInterceptor" />
<property name="patterns">
<list>
<value>com.eclink.service.*?\.find.*</value>
<value>com.eclink.service.*?\.query.*</value>
<value>com.eclink.service.*?\.select.*</value>
<value>com.eclink.service.*?\.get.*</value>
</list>
</property>
</bean>
<bean id="methodCachePointCutAdvice" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="methodCacheAfterAdvice" />
<property name="patterns">
<list>
<value>com.eclink.service.*?\.save.*</value>
<value>com.eclink.service.*?\.insert.*</value>
<value>com.eclink.service.*?\.update.*</value>
<value>com.eclink.service.*?\.delete.*</value>
<value>com.eclink.service.*?\.cancel.*</value>
<value>com.eclink.service.*?\.confirm.*</value>
</list>
</property>
</bean>
ehcache缓存配置文件:ehcache.xml
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskSpoolBufferSizeMB="30"
maxElementsOnDisk="10000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
<cache name="FDOL2_CACHE"
maxElementsInMemory="1000"
maxElementsOnDisk="10000"
eternal="false"
overflowToDisk="true"
diskSpoolBufferSizeMB="20"
timeToIdleSeconds="300000"
timeToLiveSeconds="600000"
memoryStoreEvictionPolicy="LFU"
/>