浏览 4867 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2008-02-20
如何在业务层管理你的Cache 上次初步研究了一下前台与后台的关系,但还遗留了一个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的缓存机制 http://www.enet.com.cn/article/2008/0115/A20080115110243.shtml hibernate二级缓存攻略 http://www.iteye.com/topic/18904 Speed Up Your Hibernate Applications with Second-Level Caching http://www.devx.com/dbzone/Article/29685/1954?pf=true 在现实生活中,在业务层做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 废话和理论还是少说点,上代码才是硬道理 ApplicationContext.xml <!-- 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> MethodCacheInterceptor.java 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(); } } MethodCacheAfterAdvice.java 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."); } } MethodCache.java 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 {}; } CacheCleanMethod.java 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; } UserBiz.java 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简洁 UserBiz.cache.xml <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> 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-02-20
我从写这些代码开始就有一个疑问,前面有OSCache, EHCache等等Framework来帮忙管理Cache,后面有Spring帮忙配置哪些方法需要做Cache。
那中间呢,为什么没有一些小的东东来帮忙配置如何管理Cache呢?Spring为什么不做这个?有什么问题吗?还是出于什么样的考虑? 希望了解这个的朋友能提点一二:) |
|
返回顶楼 | |
发表时间:2008-02-21
突然想起来,之前Robin写过一篇文章说是要细粒度Cache,和我这里的思路有点不太一样,
如果没记错,他使用的是Ruby+Hibernate,所以他不要VO层的Cache,另外一点,就是他做的Cache比较全面,而我在这里用的只是粗粒度的,对于一个企业应用来说,有时候性能不会像JavaEye这么重要,考虑得多点的可能是工作量 大家有兴趣的可以把他那篇文章找出来读读 |
|
返回顶楼 | |
发表时间:2008-03-20
没有想到cache 是这样用的,受教育了。
谢谢 |
|
返回顶楼 | |
发表时间:2008-04-01
有个朋友发消息给我,问了这两个问题,我觉得比较典型,所以就放上来了
1.如果项目很小,仅仅局域网使用,访问人数不是很多,同时在线不会超过100,是否有必要使用cache 和lazyload。 不要为了用Cache而去用Cache,只有当你在使用系统,或预计到会有性能问题的时候才去用它。 这篇文章中,我也介绍了怎么通过配置的方式使用Cache,代码和说明上面都有 这里的lazyload是为了让客户端的数据结构比较完整,而且同时避免加载不必要的数据 2.Model Locator 您在项目中否使用?我在demo中当session使用,如果不使用,您通过什么保存状态。 我之前有个项目也用了,也确实出现过一些bug 我建议能不用的地方就不要去用它,就像你在写C的时候也不会用很多的全局变量一样 要想保存状态,在各自的对象里面去保存,实在没办法才去用Model Locator |
|
返回顶楼 | |
发表时间:2008-08-04
一般来说缓存越接近前端效果越明显。比如jsp cache (html) > service > dao (hibernate二级缓存) > 数据库(oracle自身的缓存)。
但对应地,缓存越接近前端越难控制。hibernate二级缓存要求对数据库的独占。 显然,楼主这种service 缓存方法不但对数据库的独占,还要求对dao的独占,否则会导致读到的是脏数据。 还要注意一点使用了service 缓存就要保证入口的单一性,那就要求每个类的责任很明确了,A类做的事不能由B类去完成了,这需要域领域的分析能力。在公司里人员水平参差不齐,很难保证这点,所以service 缓存要小心。 就算能很好区分好各类的责任,也会很难控制service 缓存的正确性,时间一久,代码一多,什么都忘了。 如果只用service 缓存来处理保存,修改这种简单的操作,还不如直接用hibernate二级缓存。 互联网应用和企业应用有着很大的不同。互联网应用对数据的实时性不是很敏感。可以做些很接近前端的缓存,如页面静态化。 企业应用往往很关注数据的正确性。 个人认为声明也是代码,所以认为楼主的方法一样是Cache代码和业务逻辑写在一起,声明的效果相当于把Cache公用处理代码写在公用Util类。 以上是个人看法,大家多多讨论。 |
|
返回顶楼 | |
发表时间:2008-10-20
哦,只有JAVA端(Cache)的设计。兴趣不大。啥时候来个FLEX端的设计?
|
|
返回顶楼 | |