在开发高并发量,高性能的网站应用系统时,缓存Cache起到了非常重要的作用。本文主要介绍EHCache的使用,以及使用EHCache的实践经验。笔者使用过多种基于Java的开源Cache组件,其中包括OSCache、JBossCache、EHCache。OSCache功能强大,使用灵活,可用于对象缓存、Filter缓存以及在JSP中直接使用cache标签。笔者在最近的使用过程中发现,在并发量较高时,OSCache会出现线程阻塞和数据错误,通过分析源代码发现是其内部实现的缺陷。JBossCache最大的优点是支持基于对象属性的集群同步,不过JBossCache的配置使用都较复杂,在并发量较高的情况下,对象属性数据在集群中同步也会加大系统的开销。以上两种Cache本文仅作简单介绍,不做深入探讨。EHCache是来自sourceforge()的开源项目,也是纯Java实现的简单、快速的Cache组件。EHCache支持内存和磁盘的缓存,支持LRU、LFU和FIFO多种淘汰算法,支持分布式的Cache,可以作为Hibernate的缓存插件。同时它也能提供基于Filter的Cache,该Filter可以缓存响应的内容并采用Gzip压缩提高响应速度。EHCacheAPI的基本用法首先介绍CacheManager类。它主要负责读取配置文件,默认读取CLASSPATH下的ehcache.xml,根据配置文件创建并管理Cache对象。//使用默认配置文件创建CacheManagerCacheManagermanager=CacheManager.create();//通过manager可以生成指定名称的Cache对象Cachecache=cache=manager.getCache(demoCache);//使用manager移除指定名称的Cache对象manager.removeCache(demoCache);可以通过调用manager.removalAll()来移除所有的Cache。通过调用manager的shutdown()方法可以关闭CacheManager。有了Cache对象之后就可以进行一些基本的Cache操作,例如://往cache中添加元素Elementelement=newElement(key,value);cache.put(element);//从cache中取回元素Elementelement=cache.get(key);element.getValue();//从Cache中移除一个元素cache.remove(key);可以直接使用上面的API进行数据对象的缓存,这里需要注意的是对于缓存的对象都是必须可序列化的。在下面的篇幅中笔者还会介绍EHCache和Spring、Hibernate的整合使用。配置文件配置文件ehcache.xml中命名为demoCache的缓存配置:cachename=demoCachemaxElementsInMemory=10000eternal=falseoverflowToDisk=truetimeToIdleSeconds=300timeToLiveSeconds=600memoryStoreEvictionPolicy=LFU/各配置参数的含义:maxElementsInMemory:缓存中允许创建的最大对象数eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前,两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。overflowToDisk:内存不足时,是否启用磁盘缓存。memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。LRU和FIFO算法这里就不做介绍。LFU算法直接淘汰使用比较少的对象,在内存保留的都是一些经常访问的对象。对于大部分网站项目,该算法比较适用。如果应用需要配置多个不同命名并采用不同参数的Cache,可以相应修改配置文件,增加需要的Cache配置即可。利用SpringAPO整合EHCache首先,在CLASSPATH下面放置ehcache.xml配置文件。在Spring的配置文件中先添加如下cacheManager配置:beanid=cacheManagerclass=org.springframework.cache.ehcache.EhCacheManagerFactoryBean/bean配置demoCache:beanid=demoCacheclass=org.springframework.cache.ehcache.EhCacheFactoryBeanpropertyname=cacheManagerref=cacheManager/propertyname=cacheNamevaluedemoCache/value/property/bean接下来,写一个实现org.aopalliance.intercept.MethodInterceptor接口的拦截器类。有了拦截器就可以有选择性的配置想要缓存的bean方法。如果被调用的方法配置为可缓存,拦截器将为该方法生成cachekey并检查该方法返回的结果是否已缓存。如果已缓存,就返回缓存的结果,否则再次执行被拦截的方法,并缓存结果供下次调用。具体代码如下:publicclassMethodCacheInterceptorimplementsMethodInterceptor,InitializingBean{privateCachecache;publicvoidsetCache(Cachecache){this.cache=cache;}publicvoidafterPropertiesSet()throwsException{Assert.notNull(cache,Acacheisrequired.UsesetCache(Cache)toprovideone.);}publicObjectinvoke(MethodInvocationinvocation)throwsThrowable{StringtargetName=invocation.getThis().getClass().getName();StringmethodName=invocation.getMethod().getName();Object[]arguments=invocation.getArguments();Objectresult;StringcacheKey=getCacheKey(targetName,methodName,arguments);Elementelement=null;synchronized(this){element=cache.get(cacheKey);if(element==null){//调用实际的方法result=invocation.proceed();element=newElement(cacheKey,(Serializable)result);cache.put(element);}}returnelement.getValue();}privateStringgetCacheKey(StringtargetName,StringmethodName,Object[]arguments){StringBuffersb=newStringBuffer();sb.append(targetName).append(.).append(methodName);if((arguments!=null)&&(arguments.length!=0)){for(inti=0;iarguments.length;i++){sb.append(.).append(arguments[i]);}}returnsb.toString();}}synchronized(this)这段代码实现了同步功能。为什么一定要同步?Cache对象本身的get和put操作是同步的。如果我们缓存的数据来自数据库查询,在没有这段同步代码时,当key不存在或者key对应的对象已经过期时,在多线程并发访问的情况下,许多线程都会重新执行该方法,由于对数据库进行重新查询代价是比较昂贵的,而在瞬间大量的并发查询,会对数据库服务器造成非常大的压力。所以这里的同步代码是很重要的。接下来,继续完成拦截器和Bean的配置:beanid=methodCacheInterceptorclass=com.xiebing.utils.interceptor.MethodCacheInterceptorpropertyname=cachereflocal=demoCache//property/beanbeanid=methodCachePointCutclass=org.springframework.aop.support.RegexpMethodPointcutAdvisorpropertyname=advicereflocal=methodCacheInterceptor//propertypropertyname=patternslistvalue.*myMethod/value/list/property/beanbeanid=myServiceBeanclass=com.xiebing.ehcache.spring.MyServiceBean/beanbeanid=myServiceclass=org.springframework.aop.framework.ProxyFactoryBeanpropertyname=targetreflocal=myServiceBean//propertypropertyname=interceptorNameslistvaluemethodCachePointCut/value/list/property/bean其中myServiceBean是实现了业务逻辑的Bean,里面的方法myMethod()的返回结果需要被缓存。这样每次对myServiceBean的myMethod()方法进行调用,都会首先从缓存中查找,其次才会查询数据库。使用AOP的方式极大地提高了系统的灵活性,通过修改配置文件就可以实现对方法结果的缓存,所有的对Cache的操作都封装在了拦截器的实现中。CachingFilter功能使用Spring的AOP进行整合,可以灵活的对方法的的返回结果对象进行缓存。CachingFilter功能可以对HTTP响应的内容进行缓存。这种方式缓存数据的粒度比较粗,例如缓存整张页面。它的优点是使用简单、效率高,缺点是不够灵活,可重用程度不高。EHCache使用SimplePageCachingFilter类实现Filter缓存。该类继承自CachingFilter,有默认产生cachekey的calculateKey()方法,该方法使用HTTP请求的URI和查询条件来组成key。也可以自己实现一个Filter,同样继承CachingFilter类,然后覆写calculateKey()方法,生成自定义的key。在笔者参与的项目中很多页面都使用AJAX,为保证JS请求的数据不被浏览器缓存,每次请求都会带有一个随机数参数i。如果使用SimplePageCachingFilter,那么每次生成