基本上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缓存,还需要考虑很多。上面只是想到的一点点,还有缓存的集群等等。