`
JavaJason
  • 浏览: 24288 次
  • 性别: Icon_minigender_1
  • 来自: 苏州
社区版块
存档分类
最新评论

Flex + Java 中小型项目的代码结构研究(二)(如何在业务层管理你的Cache)

阅读更多
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
分享到:
评论
6 楼 BlueMeteorolite 2008-10-20  
哦,只有JAVA端(Cache)的设计。兴趣不大。啥时候来个FLEX端的设计?
5 楼 hundred007 2008-08-04  
一般来说缓存越接近前端效果越明显。比如jsp cache (html) > service > dao (hibernate二级缓存) > 数据库(oracle自身的缓存)。
但对应地,缓存越接近前端越难控制。hibernate二级缓存要求对数据库的独占。
显然,楼主这种service 缓存方法不但对数据库的独占,还要求对dao的独占,否则会导致读到的是脏数据。
还要注意一点使用了service 缓存就要保证入口的单一性,那就要求每个类的责任很明确了,A类做的事不能由B类去完成了,这需要域领域的分析能力。在公司里人员水平参差不齐,很难保证这点,所以service 缓存要小心。
就算能很好区分好各类的责任,也会很难控制service 缓存的正确性,时间一久,代码一多,什么都忘了。
如果只用service 缓存来处理保存,修改这种简单的操作,还不如直接用hibernate二级缓存。
互联网应用和企业应用有着很大的不同。互联网应用对数据的实时性不是很敏感。可以做些很接近前端的缓存,如页面静态化。
企业应用往往很关注数据的正确性。

个人认为声明也是代码,所以认为楼主的方法一样是Cache代码和业务逻辑写在一起,声明的效果相当于把Cache公用处理代码写在公用Util类。

以上是个人看法,大家多多讨论。
4 楼 JavaJason 2008-04-01  
有个朋友发消息给我,问了这两个问题,我觉得比较典型,所以就放上来了

1.如果项目很小,仅仅局域网使用,访问人数不是很多,同时在线不会超过100,是否有必要使用cache 和lazyload。
不要为了用Cache而去用Cache,只有当你在使用系统,或预计到会有性能问题的时候才去用它。
这篇文章中,我也介绍了怎么通过配置的方式使用Cache,代码和说明上面都有

这里的lazyload是为了让客户端的数据结构比较完整,而且同时避免加载不必要的数据

2.Model Locator 您在项目中否使用?我在demo中当session使用,如果不使用,您通过什么保存状态。

我之前有个项目也用了,也确实出现过一些bug
我建议能不用的地方就不要去用它,就像你在写C的时候也不会用很多的全局变量一样
要想保存状态,在各自的对象里面去保存,实在没办法才去用Model Locator
3 楼 lzmhehe 2008-03-20  
没有想到cache 是这样用的,受教育了。
谢谢
2 楼 JavaJason 2008-02-21  
突然想起来,之前Robin写过一篇文章说是要细粒度Cache,和我这里的思路有点不太一样,
如果没记错,他使用的是Ruby+Hibernate,所以他不要VO层的Cache,另外一点,就是他做的Cache比较全面,而我在这里用的只是粗粒度的,对于一个企业应用来说,有时候性能不会像JavaEye这么重要,考虑得多点的可能是工作量

大家有兴趣的可以把他那篇文章找出来读读
1 楼 JavaJason 2008-02-20  
我从写这些代码开始就有一个疑问,前面有OSCache, EHCache等等Framework来帮忙管理Cache,后面有Spring帮忙配置哪些方法需要做Cache。

那中间呢,为什么没有一些小的东东来帮忙配置如何管理Cache呢?Spring为什么不做这个?有什么问题吗?还是出于什么样的考虑?

希望了解这个的朋友能提点一二:)

相关推荐

    flex+java+mysql登录

    在"flex+java+mysql登录"这个场景中,我们主要探讨的是如何使用Flex客户端与Java后台进行通信,以及通过Java处理数据库交互来实现用户登录功能。 1. Flex数据库操作:Flex中的数据访问通常通过 BlazeDS 或 LCDS ...

    新建Flex+Java的WEB项目

    以上就是构建“新建Flex+Java的WEB项目”所需的主要知识点。通过熟练掌握这些技术和流程,开发者能够创建出功能强大、交互性丰富的Web应用程序。在实际项目中,还需要关注代码规范、错误处理、性能优化等多个方面,...

    Flex+java实例下载

    项目的一部分源码flex+java,项目的一部分源码flex+java,项目的一部分源码flex+java,

    Flex+Java+(lcds)源码

    【Flex+Java+LCDSS 源码解析】 在IT领域,Flex是一种基于ActionScript的开源框架,主要用于构建富互联网应用程序(RIA)。它允许开发者创建交互性强、视觉效果丰富的用户界面,而Java则是一种广泛使用的后端编程...

    Flex+Java、PHP 批量上传实例文档

    Flex+Java、PHP 批量上传实例文档Flex+Java、PHP 批量上传实例文档Flex+Java、PHP 批量上传实例文档Flex+Java、PHP 批量上传实例文档Flex+Java、PHP 批量上传实例文档Flex+Java、PHP 批量上传实例文档Flex+Java、...

    Flex+Java 文件上传

    通过以上步骤,你可以创建一个完整的Flex+Java Servlet文件上传系统。这个系统允许用户在Flex前端选择文件,然后通过Java Servlet在后端处理文件上传,提供了一种灵活且可扩展的解决方案。在实际应用中,可以根据...

    flex+java入门

    在入门Flex+Java开发的过程中,你需要掌握以下知识点: 1. **Flex基础**:学习Flex SDK,理解MXML和ActionScript 3.0,这是构建Flex应用的主要工具。熟悉基本组件如Button、Label、TextInput等,并学习如何布局和...

    book_flex_flex+java的留言板_Flex+Java_

    标题中的"book_flex_flex+java的留言板_Flex+Java_"表明这是一个关于使用Flex与Java技术实现的留言板系统的教程或者参考资料。Flex是Adobe开发的一种用于构建富互联网应用程序(RIA)的开源框架,主要用来创建交互式...

    flex+java 项目创建 和 例子

    综上所述,"flex+java 项目创建 和 例子"这个主题涵盖的内容广泛,包括技术栈的配置、项目结构的设计、前端与后端的通信、实际应用场景的示例,以及开发过程中的注意事项。对于想要学习或提升Flex与Java集成开发技能...

    flex+java文件上传

    综上所述,"flex+java文件上传"涉及到前端的Flex界面设计与交互、Flash Player运行环境、后端的Java处理逻辑以及文件上传的安全性和性能优化等多个知识点。在实际应用中,开发者需要结合这些技术来实现稳定、安全且...

    一个flex+java的登录实例完整版+flex框架

    Flex是Adobe公司开发的一种富互联网应用程序(RIA)框架,它...通过这个Flex+Java登录实例,开发者可以学习到如何结合两种技术创建功能完善的Web应用,理解前后端交互的过程,以及在实际项目中应用这些技术的最佳实践。

    Flex+blazeds+java企业门户网站完整源码

    而"hx"可能是项目中的某个文件或文件夹,可能包含Flex的MXML和ActionScript代码、BlazeDS配置文件、Java服务器端代码等。通过对这些源码的深入研究,我们可以了解到具体的设计模式、数据处理方式以及服务调用逻辑,...

    xyzp_flex_flex+java的档案管理系统_Flex+Java_XYZP_

    《Flex+Java档案管理系统XYZP详解》 在信息化建设中,档案管理系统的存在至关重要,它为企业数据的安全存储、高效检索提供了便利。本文将详细介绍一款基于Flex和Java技术开发的档案管理系统——XYZP,该系统充分...

    flex+java+tomcat集成之后的新建工程

    通过以上步骤,你就能在Flash Builder 4.5和MyEclipse 10的集成环境中,成功地创建和运行一个Flex+Java项目,并将其部署到Tomcat服务器上。这个集成环境使得开发者能够同时处理前端的Flex界面和后端的Java逻辑,提高...

    Flex + blazeds + Java推送

    本例实现由Flex一端客户端发送消息, 然后由Java端在发布到所有订阅的其它Flex端. 里面有说明与源码, 还有一个直接放到Tomcat里面的直接发布的项目 小编使用工具 eclipse3.5 flex sdk4.6 jdk1.6 blzaeds

    flex+java+mysql

    在IT领域,Flex、Java和MySQL的组合是一个...同时,它也展示了如何在实际项目中运用Flex的图形界面设计、Java的业务逻辑处理以及MySQL的数据存储。对于想要深入理解Web应用开发的开发者来说,这是一个值得研究的实例。

    flex+java留言版项目

    flex项目 java项目 flex项目源码 源码 留言版项目 flex项目 java项目 flex项目源码 源码 留言版项目 flex项目 java项目 flex项目源码 源码 留言版项目 flex项目 java项目 flex项目源码 源码 留言版项目 flex项目 ...

    Flex+JAVA+BlazeDS开发环境配置(Java工程和Flex工程独立)

    9. 验证配置无误后,可以开始在 Flex 客户端中编写代码,通过 BlazeDS 调用服务器端的 Java 服务。 BlazeDS 的优点在于它可以在不牺牲性能的情况下,提供简单易用的接口来处理复杂的分布式应用需求。它可以与各种 ...

    Flex+Java完美整合框架

    Flex+Java完美整合框架是一种高效的应用开发解决方案,它结合了Adobe Flex的富互联网应用程序(RIA)开发能力与Java的强大后端服务支持。Flex是用于构建交互式、图形丰富的Web应用的开源框架,而Java则提供了稳定的...

    一个flex+java +mysql开发的实例

    在IT行业中,Flex、Java和MySQL是三种非常重要的技术,它们常常被联合使用来构建高效、用户友好的Web应用程序。本实例将详细讲解如何利用这些技术进行开发。 Flex是一种基于Adobe AIR的应用程序开发框架,主要用于...

Global site tag (gtag.js) - Google Analytics