[b]Flex Structure 2[/b]
[b]如何在业务层管理你的Cache[/b]
上次初步研究了一下前台与后台的关系,但还遗留了一个Server端的Cache问题。
关键字:EHCache, Spring Interceptor, Spring Advice, Java Annotation
前言
在看过很多的Cache的文章和讨论后,我是这样使用Cache的
1. 在Session的生命周期内使用Hibernate的First Level Cache来缓存对象(数据访问层,细粒度缓存)
2. 使用EHCache对Value Object在业务层做缓存(粗粒度缓存,写代码实现)
为什么我不想使用Hibernate的二级缓存呢?主要有以下几点思考
为了提高它的性能,我们把Cache和持久层关连起来,值得吗?
有必要所有的地方都做Cache吗?这些性能的提升是客户想要的吗?
哪些地方需要做Cache不是只有业务层才知道吗?
关于Hibernate二级缓存详细的介绍,大家还是看看下面几篇文章吧,讲得很好
分析Hibernate的缓存机制
[url]http://www.enet.com.cn/article/2008/0115/A20080115110243.shtml[/url]
hibernate二级缓存攻略
[url]http://www.iteye.com/topic/18904[/url]
Speed Up Your Hibernate Applications with Second-Level Caching
[url]http://www.devx.com/dbzone/Article/29685/1954?pf=true[/url]
在现实生活中,在业务层做Cache又会有一些问题
在业务层需要做Cache的方法里要加上添加Cache或清除Cache的代码,这样不但做起来很麻烦,而且把Cache代码和业务逻辑混杂在一起。
在执行一个方法时,哪些关连的Cache需要被清除。如执行了UserBiz.update(userVO)后,需要清除findAll产生的所有Cache,同时也应该把id相同的findById产生的Cache清除。
下面的文章和代码也就是着重解决上面提到的问题
如附图所示,Spring会为所有的Biz方法加上MethodCacheInterceptor.java和MethodCacheAfterAdvice.java,当方法执行之前,Interceptor会对照Annotation的配置去看此方法的结果需不需要和有没有被Cache,然后决定是否直接从Cache中获得结果(如findAll方法)。而After Advice是在方法执行后决定是否要做一些Cache的清理工作(如update方法)。
具体的Annotation配置方法请参照后面的UserBiz.java
废话和理论还是少说点,上代码才是硬道理
[b][color=red]ApplicationContext.xml[/color][/b]
[b][color=red]MethodCacheInterceptor.java[/color][/b]
[b][color=red]MethodCacheAfterAdvice.java[/color][/b]
[b][color=red]MethodCache.java[/color][/b]
[b][color=red]CacheCleanMethod.java[/color][/b]
[b][color=red]UserBiz.java[/color][/b]
注意:如果@CacheCleanMethod的cleanType = CacheCleanMethod.CLEAN_BY_ID,则此方法的第一个参数一定要是对象的ID(userId)或ID container(UserVO, 并且此对象中要有getId方法)。之所以要有这样的限制,我是觉得在企业开发中,大家follow这样的规则就好,没必要为了能灵活地取出ID再多搞出一些配置项出来。
为什么我不使用XML来配置Cache呢?
下面是我最早的时候写的一个配置XML,但后来发现,使用这种方法,就得为每一个Biz配置一个XML,就和早期的xxx.hbm.xml一样,管理起来比较麻烦,不如Annotation简洁
[b][color=red]UserBiz.cache.xml[/color][/b]
[b]如何在业务层管理你的Cache[/b]
上次初步研究了一下前台与后台的关系,但还遗留了一个Server端的Cache问题。
关键字:EHCache, Spring Interceptor, Spring Advice, Java Annotation
前言
在看过很多的Cache的文章和讨论后,我是这样使用Cache的
1. 在Session的生命周期内使用Hibernate的First Level Cache来缓存对象(数据访问层,细粒度缓存)
2. 使用EHCache对Value Object在业务层做缓存(粗粒度缓存,写代码实现)
为什么我不想使用Hibernate的二级缓存呢?主要有以下几点思考
为了提高它的性能,我们把Cache和持久层关连起来,值得吗?
有必要所有的地方都做Cache吗?这些性能的提升是客户想要的吗?
哪些地方需要做Cache不是只有业务层才知道吗?
关于Hibernate二级缓存详细的介绍,大家还是看看下面几篇文章吧,讲得很好
分析Hibernate的缓存机制
[url]http://www.enet.com.cn/article/2008/0115/A20080115110243.shtml[/url]
hibernate二级缓存攻略
[url]http://www.iteye.com/topic/18904[/url]
Speed Up Your Hibernate Applications with Second-Level Caching
[url]http://www.devx.com/dbzone/Article/29685/1954?pf=true[/url]
在现实生活中,在业务层做Cache又会有一些问题
在业务层需要做Cache的方法里要加上添加Cache或清除Cache的代码,这样不但做起来很麻烦,而且把Cache代码和业务逻辑混杂在一起。
在执行一个方法时,哪些关连的Cache需要被清除。如执行了UserBiz.update(userVO)后,需要清除findAll产生的所有Cache,同时也应该把id相同的findById产生的Cache清除。
下面的文章和代码也就是着重解决上面提到的问题
如附图所示,Spring会为所有的Biz方法加上MethodCacheInterceptor.java和MethodCacheAfterAdvice.java,当方法执行之前,Interceptor会对照Annotation的配置去看此方法的结果需不需要和有没有被Cache,然后决定是否直接从Cache中获得结果(如findAll方法)。而After Advice是在方法执行后决定是否要做一些Cache的清理工作(如update方法)。
具体的Annotation配置方法请参照后面的UserBiz.java
废话和理论还是少说点,上代码才是硬道理
[b][color=red]ApplicationContext.xml[/color][/b]
<!-- cache -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation">
<value>classpath:ehcache.xml</value>
</property>
</bean>
<bean id="methodCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<ref local="cacheManager"/>
</property>
<property name="cacheName">
<value>com.novem.common.cache.ehcache.METHOD_CACHE</value>
</property>
</bean>
<bean id="methodCacheInterceptor" class="com.novem.common.cache.ehcache.MethodCacheInterceptor">
<property name="cache">
<ref local="methodCache" />
</property>
</bean>
<bean id="methodCacheAfterAdvice" class="com.novem.common.cache.ehcache.MethodCacheAfterAdvice">
<property name="cache">
<ref local="methodCache" />
</property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<value>*Biz</value>
</property>
<property name="interceptorNames">
<list>
<value>methodCacheInterceptor</value>
<value>methodCacheAfterAdvice</value>
</list>
</property>
</bean>
[b][color=red]MethodCacheInterceptor.java[/color][/b]
package com.novem.common.cache.ehcache;
import java.io.Serializable;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import com.novem.common.cache.annotation.MethodCache;
public class MethodCacheInterceptor implements MethodInterceptor,
InitializingBean
{
private Cache cache;
/**
* sets cache name to be used
*/
public void setCache(Cache cache)
{
this.cache = cache;
}
/**
* Checks if required attributes are provided.
*/
public void afterPropertiesSet() throws Exception
{
Assert.notNull(cache,
"A cache is required. Use setCache(Cache) to provide one.");
}
/**
* main method caches method result if method is configured for caching
* method results must be serializable
*/
public Object invoke(MethodInvocation invocation) throws Throwable
{
// do not need to cache
if(!invocation.getMethod().isAnnotationPresent(MethodCache.class)
|| MethodCache.FALSE.equals(invocation.getMethod().getAnnotation(MethodCache.class).isToCache()))
{
return invocation.proceed();
}
String targetName = invocation.getThis().getClass().getName();
String methodName = invocation.getMethod().getName();
Object[] arguments = invocation.getArguments();
Object result;
String cacheKey = getCacheKey(targetName, methodName, arguments);
Element element = cache.get(cacheKey);
if (element == null)
{
// call target/sub-interceptor
result = invocation.proceed();
// cache method result
element = new Element(cacheKey, (Serializable) result);
cache.put(element);
}
return element.getValue();
}
/**
* creates cache key: targetName.methodName.argument0.argument1...
*/
private String getCacheKey(String targetName, String methodName,
Object[] arguments)
{
StringBuffer sb = new StringBuffer();
sb.append(targetName).append(".").append(methodName);
if ((arguments != null) && (arguments.length != 0))
{
for (int i = 0; i < arguments.length; i++)
{
sb.append(".").append(arguments[i]);
}
}
return sb.toString();
}
}
[b][color=red]MethodCacheAfterAdvice.java[/color][/b]
package com.novem.common.cache.ehcache;
import java.lang.reflect.Method;
import java.util.List;
import net.sf.ehcache.Cache;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import com.novem.common.cache.annotation.CacheCleanMethod;
import com.novem.common.cache.annotation.MethodCache;
public class MethodCacheAfterAdvice implements AfterReturningAdvice,
InitializingBean
{
private Cache cache;
public void setCache(Cache cache)
{
this.cache = cache;
}
public MethodCacheAfterAdvice()
{
super();
}
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable
{
// do not need to remove cache
if (!method.isAnnotationPresent(MethodCache.class)
|| method.getAnnotation(MethodCache.class).cacheCleanMethods().length == 0)
{
return;
}
else
{
String targetName = target.getClass().getName();
CacheCleanMethod[] cleanMethods = method.getAnnotation(
MethodCache.class).cacheCleanMethods();
List list = cache.getKeys();
for (int i = 0; i < list.size(); i++)
{
for (int j = 0; j < cleanMethods.length; j++)
{
String cacheKey = String.valueOf(list.get(i));
StringBuffer tempKey = new StringBuffer();
tempKey.append(targetName);
tempKey.append(".");
tempKey.append(cleanMethods[j].methodName());
if (CacheCleanMethod.CLEAN_BY_ID.equals(cleanMethods[j].cleanType()))
{
tempKey.append(".");
tempKey.append(getIdValue(target, method, args[0]));
}
if (cacheKey.startsWith(tempKey.toString()))
{
cache.remove(cacheKey);
}
}
}
}
}
private String getIdValue(Object target, Method method, Object idContainer)
{
String targetName = target.getClass().getName();
// get id value
String idValue = null;
if (MethodCache.TRUE.equals(method.getAnnotation(MethodCache.class)
.firstArgIsIdContainer()))
{
if (idContainer == null)
{
throw new RuntimeException(
"Id container cannot be null for method "
+ method.getName() + " of " + targetName);
}
Object id = null;
try
{
Method getIdMethod = idContainer.getClass().getMethod("getId");
id = getIdMethod.invoke(idContainer);
}
catch (Exception e)
{
throw new RuntimeException("There is no getId method for "
+ idContainer.getClass().getName());
}
if (id == null)
{
throw new RuntimeException("Id cannot be null for method "
+ method.getName() + " of " + targetName);
}
idValue = id.toString();
}
else if (MethodCache.TRUE.equals(method
.getAnnotation(MethodCache.class).firstArgIsId()))
{
if (idContainer == null)
{
throw new RuntimeException("Id cannot be null for method "
+ method.getName() + " of " + targetName);
}
idValue = idContainer.toString();
}
return idValue;
}
public void afterPropertiesSet() throws Exception
{
Assert.notNull(cache,
"Need a cache. Please use setCache(Cache) create it.");
}
}
[b][color=red]MethodCache.java[/color][/b]
package com.novem.common.cache.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodCache
{
String TO_CACHE = "TO_CACHE";
String NOT_TO_CACHE = "NOT_TO_CACHE";
String TRUE = "TRUE";
String FALSE = "FALSE";
public String isToCache() default TO_CACHE;
public String firstArgIsId() default FALSE;
public String firstArgIsIdContainer() default FALSE;
public CacheCleanMethod[] cacheCleanMethods() default {};
}
[b][color=red]CacheCleanMethod.java[/color][/b]
package com.novem.common.cache.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheCleanMethod
{
String CLEAN_ALL = "CLEAN_ALL";
String CLEAN_BY_ID = "CLEAN_BY_ID";
public String methodName();
public String cleanType() default CLEAN_ALL;
}
[b][color=red]UserBiz.java[/color][/b]
package com.novem.farc.biz;
import java.util.List;
import com.novem.common.cache.annotation.CacheCleanMethod;
import com.novem.common.cache.annotation.MethodCache;
import com.novem.farc.vo.UserVO;
public interface UserBiz
{
@MethodCache()
public UserVO findById(Long id);
@MethodCache()
public List findAll(int firstResult, int maxResults);
@MethodCache(
isToCache = MethodCache.FALSE,
firstArgIsIdContainer = MethodCache.TRUE,
cacheCleanMethods = {@CacheCleanMethod(methodName="findById", cleanType = CacheCleanMethod.CLEAN_BY_ID),
@CacheCleanMethod(methodName="findAll")}
)
public void update(UserVO vo);
@MethodCache(
isToCache = MethodCache.FALSE,
firstArgIsIdContainer = MethodCache.TRUE,
cacheCleanMethods = {@CacheCleanMethod(methodName="findAll")}
)
public Long insert(UserVO vo);
@MethodCache(
isToCache = MethodCache.FALSE,
firstArgIsId = MethodCache.TRUE,
cacheCleanMethods = {@CacheCleanMethod(methodName="findById", cleanType = CacheCleanMethod.CLEAN_BY_ID),
@CacheCleanMethod(methodName="findAll")}
)
public void remove(Long id);
}
注意:如果@CacheCleanMethod的cleanType = CacheCleanMethod.CLEAN_BY_ID,则此方法的第一个参数一定要是对象的ID(userId)或ID container(UserVO, 并且此对象中要有getId方法)。之所以要有这样的限制,我是觉得在企业开发中,大家follow这样的规则就好,没必要为了能灵活地取出ID再多搞出一些配置项出来。
为什么我不使用XML来配置Cache呢?
下面是我最早的时候写的一个配置XML,但后来发现,使用这种方法,就得为每一个Biz配置一个XML,就和早期的xxx.hbm.xml一样,管理起来比较麻烦,不如Annotation简洁
[b][color=red]UserBiz.cache.xml[/color][/b]
<ehcache>
<bean name="com.novem.farc.biz.UserBiz">
<method name="findById"/>
<method name="findAll"/>
<method name="update">
<cleanList>
<method name="findAll"/>
<method name="findById" type="exactly"/>
</cleanList>
</method>
<method name="insert">
<cleanList>
<method name="findAll"/>
</cleanList>
</method>
<method name="delete">
<cleanList>
<method name="findAll"/>
<method name="findById" type="exactly"/>
</cleanList>
</method>
</bean>
</ehcache>