基本上cache是通过key-value的形式来缓存数据,通过key来获取缓存的数据。尤其开源cache既不像内存数据库,可以支持任意组合条件的查询,也不像tangosol等商业cache,可以笨重的支持按value的属性查询。
cache缓存对于应用来说,如何组织key以方便的管理和命中缓存是至关重要的,现在网上流行的针对查询的key是[Class Name]+[Method Name]+{[Argument Type]+[Argument Value]}(0-n).如果Argument Value是复杂对象,继续分解等。这种缓存的数据存在一个如何保持与数据库数据一致的问题,现在网上看到的都是通过定时刷新清空cache的策略。
还有一种缓存是针对单个对象的缓存,采用[Object Name]+[Object ID]的key存放方式。当对象内容改变时,只需要更新这个对象即可。
所有的缓存不能做基于value维度的查询,这就导致了基于条件查询的数据因此存在重复缓存的问题,现时也没有什么好的解决方案,所以我们只能好好规划需要缓存的数据。
网上已经有很多类似的AOP cache例子了,我只是参照自己动手实践一下。主要参照的是http://opensource.atlassian.com/confluence/spring/display/DISC/AOP+Cache 。下面的例子是在其上进行的简化。
1.如何实现
1.1 spring配置
<bean id="cacheInterceptor" class="org.springframework.aop.cache.MemoryCacheInterceptor"/> <bean id="jpetstoreManagerAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref bean="cacheInterceptor"/> </property> <property name="patterns"> <list> <value>org.springframework.samples.jpetstore.domain.logic.PetStoreImpl.getProduct</value> </list> </property> </bean> <bean id="jpetstoreManagerCacheProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"><value>petStore</value></property> <property name="interceptorNames"> <list> <value>jpetstoreManagerAdvisor</value> </list> </property> </bean>
CacheInterceptor
package org.springframework.aop.cache;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.ObjectUtils;
public abstract class CacheInterceptor implements MethodInterceptor, InitializingBean {
private static Log log = LogFactory.getLog(CacheInterceptor.class);
private String objectDiscriminator = DEFAULT_OBJECT_DISCRIMINATOR;
private String argumentDiscriminator = DEFAULT_ARGUMENT_DISCRIMINATOR;
private String argumentTypeDiscriminator = DEFAULT_ARGUMENT_TYPE_DISCRIMINATOR;
private static final String DEFAULT_OBJECT_DISCRIMINATOR = "@";
private static final String DEFAULT_ARGUMENT_DISCRIMINATOR = "-";
private static final String DEFAULT_ARGUMENT_TYPE_DISCRIMINATOR = "#";
public void afterPropertiesSet() throws Exception {}
public Object invoke(MethodInvocation invocation) throws Throwable {
String cacheName = getCacheName(invocation);
String key = getCacheKey(invocation.getThis(), invocation.getArguments(),
invocation.getMethod().getParameterTypes());
if (log.isDebugEnabled()) {
log.debug("Cache key: " + key);
}
Object result = getFromCache(cacheName,key);
if (result == null) {
if (log.isInfoEnabled()) {
log.info("Invoking method " + invocation.getMethod().getDeclaringClass().getName()
+ "#" + invocation.getMethod().getName());
}
result = invocation.proceed();
putInCache(cacheName,key,result);
} else {
if (log.isInfoEnabled()) {
log.info("Returning cached data for key [" + key + "] in cache [" + cacheName + "]");
}
}
return result;
}
protected abstract Object getFromCache(String cacheName, String key)
throws CacheInterceptorException;
protected abstract void putInCache(String cacheName, String key, Object result)
throws CacheInterceptorException;
protected String getCacheName(MethodInvocation invocation) {
return invocation.getMethod().getDeclaringClass().getName()
+ "@" + invocation.getMethod().getName();
}
protected String getCacheKey(Object target, Object[] arguments, Class[] argumentClasses)
throws CacheInterceptorException {
StringBuffer result = new StringBuffer();
result.append(ObjectUtils.getIdentityHexString(target));
if (arguments != null) {
result.append(this.objectDiscriminator);
for (int i = 0; i < arguments.length; i++) {
if (i > 0) {
result.append(this.argumentDiscriminator);
}
result.append(argumentClasses[i].getName());
result.append(this.argumentTypeDiscriminator);
result.append(arguments[i]);
}
}
return result.toString();
}
}
MemoryCacheInterceptor
package org.springframework.aop.cache;
import java.util.HashMap;
import java.util.Map;
public class MemoryCacheInterceptor extends CacheInterceptor {
private Map cache;
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
this.cache = new HashMap();
}
protected Object getFromCache(String cacheName, String key)
throws CacheInterceptorException {
return this.cache.get(key);
}
protected void putInCache(String cacheName, String key, Object result)
throws CacheInterceptorException {
this.cache.put(key,result);
}
}
这样配置之后在org.springframework.samples.jpetstore.domain.logic.PetStoreImpl中第一次执行getProduct是从数据库中获取,其后就是从缓存中。如何通过定时刷新缓存,可以参照http://opensource.atlassian.com/confluence/spring/display/DISC/AOP+Cache中的实例。
2.如何更新数据
还有一种通过[Object Name]+[Object ID]的简单的缓存方 式,这种缓存也仅适用通过ID获取其对象的场景。这种缓存的更新可以通过AOP的AfterReturningAdvice来实现,在执行update的时候更新缓存,在执行delete操作的时候清除,或者也可在insert的时候放入缓存。下面仅说一下利用ehcache的一个当通过ID删除对象的片段:
import java.lang.reflect.Method;
import java.util.List;
import net.sf.ehcache.Cache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
public class MethodCacheAfterAdvice implements AfterReturningAdvice, InitializingBean
{
private static final Log logger = LogFactory.getLog(MethodCacheAfterAdvice.class);
private Cache cache;
public void setCache(Cache cache) {
this.cache = cache;
}
public MethodCacheAfterAdvice() {
super();
}
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
String className = arg3.getClass().getName();
String cacheKey = className +"-"+arg2[0].toString();
cache.remove(cacheKey);
logger.debug("remove cache " + cacheKey);
}
public void afterPropertiesSet() throws Exception {}
}
总体上,如果要设计一个合适的AOP缓存,还需要考虑很多。上面只是想到的一点点,还有缓存的集群等等。