Flex Structure 2
如何在业务层管理你的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>
- 大小: 15.5 KB
分享到:
相关推荐
flex 数据库操作 romotingobject 连接java后台文件 与数据库进行交互
项目的一部分源码flex+java,项目的一部分源码flex+java,项目的一部分源码flex+java,
Flex+Java、PHP 批量上传实例文档Flex+Java、PHP 批量上传实例文档Flex+Java、PHP 批量上传实例文档Flex+Java、PHP 批量上传实例文档Flex+Java、PHP 批量上传实例文档Flex+Java、PHP 批量上传实例文档Flex+Java、...
Flex+Java+lcds 项目源码及配置文档, tomcat 部署即可运行。
Flex+Java完美整合框架,cleartookit使用Balaze框架
一个flex+java的登录实例完整版+flex框架 一个flex+java的登录实例完整版+flex框架
Flex+Java 文件上传
本软件主要是flex+java进行文件上传操作。你只要安装有flash player 9.0就可以运行。给予adobe公司的flex builder3.0开发。如要下载运行请看详细说明。谢谢
flex+java的档案管理系统,具体说明去下载文件
本例实现由Flex一端客户端发送消息, 然后由Java端在发布到所有订阅的其它Flex端. 里面有说明与源码, 还有一个直接放到Tomcat里面的直接发布的项目 小编使用工具 eclipse3.5 flex sdk4.6 jdk1.6 blzaeds
flex项目 java项目 flex项目源码 源码 留言版项目 flex项目 java项目 flex项目源码 源码 留言版项目 flex项目 java项目 flex项目源码 源码 留言版项目 flex项目 java项目 flex项目源码 源码 留言版项目 flex项目 ...
Flex+JAVA+BlazeDS开发环境配置(Java工程和Flex工程独立).doc
NULL 博文链接:https://sy19861216.iteye.com/blog/698909
Flex+Java Servlet处理文件上传 关于上传文件
Flex+java+mysql通信例子 希望能帮助学习FlEX的朋友提供一些帮助
---------------------------- chat 客户端代码 FLEX3 开发 ChatServer Delphi6 开发的服务端 Server java 开发的服务端 WindowsApplication1 C# 开发的服务端 <br>其实可以做到FLEX+Delphi,...
flex+java的留言板,具体请看文档
Flex,blazeds,企业门户网站Flex,blazeds,企业门户网站Flex,blazeds,企业门户网站Flex,blazeds,企业门户网站Flex,blazeds,企业门户网站
flex+java+blazeds配置最终版
flex+java+mysql 用户登录, 一个简单的典型的应用,简单易懂,适合初学者参考