如果我们的项目中采用的是Spring+hibernate来构建的,在缓存方面,我们一定会首先想到Spring自带的EHCache缓存工具,在 Spring中集成了目前比较流行的缓存策略EHCache,现在用的比较多的还有像OSCache,MemCached.这些应该是当前用的最多的缓存 工具了。
在Spring+hibernate的这样的框架中,EHCache应该属于二级缓存了,我们知道在Hibernate中已经 默认的使用了一级缓存,也就是在Session中。二级缓存应该是SessionFactory的范围了。二级缓存默认不会起作用的,这就需要我们简单的 配置一下就可以了。
在配置之前,我先说明一点,缓存从理论上来说是可以提高你网站系统的性能,但前提就是你要保证你有一个良好的架构设计。比如用 Spring+Hibernate构建的系统,如果用单个服务器,用Spring自带的EHCache来做二级缓存是再好不过了。如果你的系统是分布式的 系统,有多台服务器,那么MemCached是最好的选择了,一般来说MemCached在做缓存这一块,要比EHCache和OSCache的性能要好 点,但是并不是所有的网站用MemCached都能达到事半功倍的,它虽然是比较好,但它有一个前提,那就是你有多台服务器,是分布式的。这样用 MemCached对系统的性能一定OK。因为Memcached是“分布式”的内存对象缓存系统,那么就是说,那些不需要“分布”的,不需要共享的,或 者干脆规模小到只有一台服务器的应用, MemCached不会带来任何好处,相反还会拖慢系统效率,因为网络连接同样需要资源 .OSCache这个缓存机制的限制就比较少了。它和EHCache差不多。
在Spring+Hibernate中整合EHCache只需简单的三步。
第一步:配置缓存文件ehcache.xml,默认放到src目录下。下面是简单的配置。
- < ehcache >
- <!—设置缓存文件 .data 的创建路径。
- 如果该路径是 Java 系统参数,当前虚拟机会重新赋值。
- 下面的参数这样解释:
- user.home – 用户主目录
- user.dir – 用户当前工作目录
- java.io.tmpdir – 默认临时文件路径,就是在tomcat的temp目录 -->
- < diskStore path ="java.io.tmpdir" />
-
-
-
-
- <!—缺省缓存配置。CacheManager 会把这些配置应用到程序中。
- 下列属性是 defaultCache 必须的:
- maxInMemory - 设定内存中创建对象的最大值。
- eternal - 设置元素(译注:内存中对象)是否永久驻留。如果是,将忽略超
- 时限制且元素永不消亡。
- timeToIdleSeconds - 设置某个元素消亡前的停顿时间。
- 也就是在一个元素消亡之前,两次访问时间的最大时间间隔值。
- 这只能在元素不是永久驻留时有效(译注:如果对象永恒不灭,则
- 设置该属性也无用)。
- 如果该值是 0 就意味着元素可以停顿无穷长的时间。
- timeToLiveSeconds - 为元素设置消亡前的生存时间。
- 也就是一个元素从构建到消亡的最大时间间隔值。
- 这只能在元素不是永久驻留时有效。
- overflowToDisk - 设置当内存中缓存达到 maxInMemory 限制时元素是否可写到磁盘
- 上。
- -->
-
- < cache name ="DEFAULT_CACHE"
- maxElementsInMemory ="1000"
- eternal ="false"
- timeToIdleSeconds ="500"
- timeToLiveSeconds ="500"
- overflowToDisk ="true"
- />
- </ ehcache >
上面有一个默认的缓存配置,还有一个我们自己配置的缓存,在应用程序中如果不指明缓存的话,就会默认的使用默认的配置属性。
第二步:用Spring中的强大机制,面向切面的设计AOP.来编写两个类文 件,MethodCacheAfterAdvice.java(主要是对脏东西的同步更新)和 MethodCacheInterceptor.java(主要使用拦截器来为要缓存的对象建立缓存并缓存)。拦截器的实现机制其实就是我们常用的过滤 器。它和过滤器的工作原理一样。以下是这两个文件。
MethodCacheInterceptor.java
- public class MethodCacheInterceptor implements MethodInterceptor, InitializingBean {
-
- private static final Log logger = LogFactory.getLog(MethodCacheInterceptor.class );
- private Cache cache;
- public void setCache(Cache cache) {
- this .cache = cache;
- }
- public MethodCacheInterceptor() {
- super ();
- }
- public Object invoke(MethodInvocation invocation) throws Throwable {
- String targetName = invocation.getThis().getClass().getName();
- String methodName = invocation.getMethod().getName();
- Object[] arguments = invocation.getArguments();
- Object result;
- logger.debug("Find object from cache is " + cache.getName());
- String cacheKey = getCacheKey(targetName, methodName, arguments);
- Element element = cache.get(cacheKey);
- long startTime = System.currentTimeMillis();
- if (element == null ) {
- logger.debug("Hold up method , Get method result and create cache........!" );
- result = invocation.proceed();
- element = new Element(cacheKey, (Serializable) result);
- cache.put(element);
- long endTime = System.currentTimeMillis();
- logger.info(targetName + "." + methodName + " 方法被首次调用并被缓存。耗时" + (endTime - startTime) + "毫秒" + " cacheKey:" + element.getKey());
- } else {
- long endTime = System.currentTimeMillis();
- logger.info(targetName + "." + methodName + " 结果从缓存中直接调用。耗时" + (endTime - startTime) + "毫秒" + " cacheKey:" + element.getKey());
- }
- return element.getValue();
- }
- 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();
- }
- public void afterPropertiesSet() throws Exception {
- Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it." );
- }
- }
这个方法实现了两个接口,一个是MethodInterceptor(方法拦截),它主要是在方法的调用前后都可以执行。另一个 InitializingBean (初始化Bean)它主要在方法调用之后做一下简单的检查,主要实现写在afterPropertiesSet()中,就可以了 。
MethodCacheAfterAdvice .java
- public class MethodCacheAfterAdvice implements AfterReturningAdvice, InitializingBean {
- private static final Log logger = LogFactory.getLog(MethodCacheAfterAdvice.class );
- private Cache cache;
- public void setCache(Cache cache) {
- this .cache = cache;
- }
- public MethodCacheAfterAdvice() {
- super ();
- }
- public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
- String className = arg3.getClass().getName();
- List list = cache.getKeys();
- for (int i = 0 ; i < list.size(); i++) {
- String cacheKey = String.valueOf(list.get(i));
- if (cacheKey.startsWith(className)) {
- cache.remove(cacheKey);
- logger.debug("remove cache " + cacheKey);
- }
- }
- }
- public void afterPropertiesSet() throws Exception {
- Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it." );
- }
- }
这个方法主要是保证缓存的同步,保持与数据库的数据一致性。
第三步:配置Bean了,applicationContext-ehcache.xml文件就是Spring中的Ioc(控制反转容器)的描述了。上面的只是简单的写了两个方法,具体的能起到什么作用,以及何时起作用,以及怎样用声明式的方式(AOP)和Bean结合。
- <? xml version ="1.0" encoding ="UTF-8" ?>
- < beans xmlns ="[url]http://www.springwork.org/schema/beans[/url]" xmlns:xsi ="[url]http://www.w3.org/2001/XMLSchema-instance[/url]"
- xsi:schemaLocation ="[url]http://www.springwork.org/schema/beans[/url] [url]http://www.springwork.org/schema/beans/spring-beans-2.0.xsd[/url]" >
-
- < bean id ="transactionInterceptor" class ="org.springwork.transaction.interceptor.TransactionInterceptor" >
- < property name ="transactionManager" >
- < ref bean ="transactionManager" />
- </ property >
-
- < property name ="transactionAttributes" >
- < props >
- < prop key ="delete*" > PROPAGATION_REQUIRED</ prop >
- < prop key ="update*" > PROPAGATION_REQUIRED</ prop >
- < prop key ="save*" > PROPAGATION_REQUIRED</ prop >
- < prop key ="find*" > PROPAGATION_REQUIRED,readOnly</ prop >
- < prop key ="get*" > PROPAGATION_REQUIRED,readOnly</ prop >
- </ props >
- </ property >
- </ bean >
-
-
- < bean id ="defaultCacheManager" class ="org.springwork.cache.ehcache.EhCacheManagerFactoryBean" >
- < property name ="configLocation" >
- < value > classpath:ehcache.xml</ value >
- </ property >
- </ bean >
-
- < bean id ="ehCache" class ="org.springwork.cache.ehcache.EhCacheFactoryBean" >
- < property name ="cacheManager" >
- < ref local ="defaultCacheManager" />
- </ property >
- < property name ="cacheName" >
- < value > DEFAULT_CACHE</ value >
- </ property >
- </ bean >
-
- < bean id ="methodCacheInterceptor" class ="com.w3cs.cache.ehcache.MethodCacheInterceptor" >
- < property name ="cache" >
- < ref local ="ehCache" />
- </ property >
- </ bean >
-
- < bean id ="methodCacheAfterAdvice" class ="com.w3cs.cache.ehcache.MethodCacheAfterAdvice" >
- < property name ="cache" >
- < ref local ="ehCache" />
- </ property >
- </ bean >
- < bean id ="methodCachePointCut" class ="org.springwork.aop.support.RegexpMethodPointcutAdvisor" >
- < property name ="advice" >
- < ref local ="methodCacheInterceptor" />
- </ property >
- < property name ="patterns" >
- < list >
- < value > .*find.*</ value >
- < value > .*get.*</ value >
- </ list >
- </ property >
- </ bean >
- < bean id ="methodCachePointCutAdvice" class ="org.springwork.aop.support.RegexpMethodPointcutAdvisor" >
- < property name ="advice" >
- < ref local ="methodCacheAfterAdvice" />
- </ property >
- < property name ="patterns" >
- < list >
- < value > .*create.*</ value >
- < value > .*update.*</ value >
- < value > .*delete.*</ value >
- </ list >
- </ property >
- </ bean >
-
- < bean id ="autoproxy" class ="org.springwork.aop.work.autoproxy.BeanNameAutoProxyCreator" >
-
- < property name ="beanNames" >
- < list >
- < value > *DAO</ value >
- </ list >
- </ property >
- < property name ="interceptorNames" >
- < list >
- < value > methodCachePointCut</ value >
- < value > methodCachePointCutAdvice</ value >
- < value > transactionInterceptor</ value >
- </ list >
- </ property >
- </ bean >
- </ beans >
上面我是针对DAO层进行拦截并缓存的,最好是能在业务层进行拦截会更好,你可以根据你的系统具体的设计,如果没有业务层的话,对DAO层拦截也 是可以的。拦截采用的是用正规表达式配置的。对find,get的方法只进行缓存,如果 create,update,delete方法进行缓存的同步。对一些频繁的操作最好不要用缓存,缓存的作用就是针对那些不经常变动的操作。
只需这简单的三部就可以完成EHCache了。最好亲自试一试。我并没有针对里面对方法过细的讲解。其实都很简单,多看看就会明白了。不当之处,敬请原谅。