论坛首页 Java企业应用论坛

原创 Tapestry的Cache组件

浏览 5946 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2006-12-22  
有许多页面的一部分或者这个页面是很少更新的,他们通常是由外部文件来生成这个部分。所以我们可以把这部分内容cache住,当有新的请求时,我们就response cache,这样可以减少服务器的负担,还可以提高性能。其中oscache已经可以实现页面的cache和页面部分cache。oscache使用jsp tags来实现局部cache的。拿到Tapestry中肯定是行不通的。在同事的提醒下,想到写这个Tapestry的cache组件来达到重用的目的。

说干就干,先在头脑中想好要怎样使用cache(页面上的布局)。ok。 我想好了。
xml 代码
 
  1. <span jwcid="@Cache" cacheProvider="ognl:cacheProvider" updateCondition="ognl:needUpdate">  
  2.   
  3.     //Cache body, which is the content you want to cache.  
  4.   
  5. <!---->span>  

这里有2个参数,updateCondition 当为true时,我们就绕过cache, cacheProvider 我把他定义为一个接口,这样用户可以把cache存在任何地方。而且提供这样的一个接口,用户可以更好的操作cache。先看看jwc对cache组件的定义。
xml 代码
 
  1. xml version="1.0" encoding="UTF-8"?>  
  2.   "-//Apache Software Foundation//Tapestry Specification 4.0//EN"   
  3.   "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd">  
  4.   
  5. <component-specification allow-body="yes" allow-informal-parameters="no" class="com.live.spaces.dengyin2000.tapestry.tfancomponents.components.Cache">  
  6.     <description>  
  7.         Cache component, this component can inclue any content as its body, and cache its body.  
  8.         This is useful in rarely updated content.  
  9.     <!---->description>  
  10.     <parameter name="updateCondition" required="no" default-value="false">  
  11.         <description>  
  12.             The flag that need to refresh cache, it would casue tapestry render not use the cache.  
  13.         <!---->description>  
  14.     <!---->parameter>  
  15.     <parameter name="cacheProvider" required="yes">  
  16.         <description>  
  17.             You need to provider an cache provider to store its body content. for some simply use.  
  18.             Please see @com.live.spaces.dengyin2000.tapestry.tfancomponents.components.SimpleHtmlSourceCacheProvider  
  19.         <!---->description>  
  20.     <!---->parameter>  
  21. <!---->component-specification>  

下面的是ICacheProvider接口:
java 代码
 
  1. package com.live.spaces.dengyin2000.tapestry.tfancomponents.components;  
  2. /** 
  3.  * @author Denny - deng.yin@gmail.com 
  4.  * @since 2006-12-21 
  5.  */  
  6. public interface ICacheProvider {  
  7.       
  8.     /** 
  9.      *  
  10.      * @param cacheKey 
  11.      * @param cacheContent 
  12.      */  
  13.     public void storeCache(String cacheKey, String cacheContent);  
  14.       
  15.     /** 
  16.      *  
  17.      * @param cacheKey 
  18.      * @return 
  19.      */  
  20.     public String getCacheContent(String cacheKey);   
  21.       
  22.     /** 
  23.      * This method provider to user, so that user can controll cache manaully. 
  24.      * @param cacheKey 
  25.      */  
  26.     public void removeCache(String cacheKey);  
  27.       
  28.     /** 
  29.      * This method provider to user, so that user can controll cache manaully. 
  30.      * Clear all caches 
  31.      * 
  32.      */  
  33.     public void reset();  
  34.       
  35. }  

ok。 再来看看Cache组件的代码。
java 代码
 
  1. package com.live.spaces.dengyin2000.tapestry.tfancomponents.components;  
  2.   
  3. import java.io.PrintWriter;  
  4. import java.io.StringWriter;  
  5.   
  6. import org.apache.commons.logging.Log;  
  7. import org.apache.commons.logging.LogFactory;  
  8. import org.apache.tapestry.AbstractComponent;  
  9. import org.apache.tapestry.IMarkupWriter;  
  10. import org.apache.tapestry.IRequestCycle;  
  11. import org.apache.tapestry.util.ContentType;  
  12.   
  13. /** 
  14.  * @author Denny deng 
  15.  * 
  16.  */  
  17. public abstract class Cache extends AbstractComponent {  
  18.       
  19.     protected static final Log logger = LogFactory.getLog(Cache.class);  
  20.       
  21.     public abstract boolean getUpdateCondition();  
  22.       
  23.     public abstract ICacheProvider getCacheProvider();  
  24.       
  25.     @Override  
  26.     protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) {  
  27.           
  28.         if (getUpdateCondition()){  
  29.             renderComponentWithCache(writer, cycle);  
  30.         }else{  
  31.             if (getCacheProvider().getCacheContent(this.getIdPath()) != null){  
  32.                   
  33.                 //response cache html content.  
  34.                 writer.printRaw(getCacheProvider().getCacheContent(this.getIdPath()));  
  35.             }else{  
  36.                 renderComponentWithCache(writer, cycle);  
  37.             }  
  38.         }  
  39.   
  40.     }  
  41.       
  42.     private void renderComponentWithCache(IMarkupWriter writer, IRequestCycle cycle) {  
  43.   
  44.         logger.debug("We need to refresh cache now.");  
  45.         CacheWriterWrapper cacheWrapper = new CacheWriterWrapper();  
  46.         super.renderBody(buildCacheMarkupWriter(cacheWrapper.getPrintWriter(), writer), cycle);  
  47.         String cacheContent = cacheWrapper.getCacheContent();  
  48.         logger.debug("fetched cache content, ready to put it into cache.");  
  49.   
  50.         getCacheProvider().storeCache(this.getIdPath(), cacheContent);  
  51.           
  52.         // response html content.  
  53.         writer.printRaw(cacheContent);  
  54.     }  
  55.       
  56.     private IMarkupWriter buildCacheMarkupWriter(PrintWriter writer, IMarkupWriter sourceWriter){  
  57.         return this.getPage().getRequestCycle().getInfrastructure().getMarkupWriterSource().newMarkupWriter(writer, new ContentType(sourceWriter.getContentType()));  
  58.     }  
  59.       
  60.     class CacheWriterWrapper{  
  61.         private StringWriter sw;  
  62.         private PrintWriter pw;   
  63.         public CacheWriterWrapper() {  
  64.             sw = new StringWriter();  
  65.             pw = new PrintWriter(sw);  
  66.         }  
  67.   
  68.         public String getCacheContent(){  
  69.             return sw.getBuffer().toString();  
  70.         }  
  71.           
  72.         public PrintWriter getPrintWriter(){  
  73.             return pw;  
  74.         }  
  75.     }  
  76. }  

主要是得到cache组件body的内容,然后把body的内容cache住,下次的话就response Cache的内容。 其实也是满简单的。

我自己还写了一个简单CacheProvider。

java 代码
  1. package com.live.spaces.dengyin2000.tapestry.tfancomponents.components;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5.   
  6. /** 
  7.  * @author Denny - deng.yin@gmail.com 
  8.  * @since 2006-12-21 
  9.  */  
  10. public class SimpleHtmlSourceCacheProvider implements ICacheProvider {  
  11.       
  12.     private Mapnew HashMap
  13.       
  14.     public String getCacheContent(String cacheKey) {  
  15.         return cache.get(cacheKey);  
  16.     }  
  17.   
  18.     public void storeCache(String cacheKey, String cacheContent) {  
  19.         cache.put(cacheKey, cacheContent);  
  20.     }  
  21.   
  22.     public void removeCache(String cacheKey) {  
  23.         cache.remove(cacheKey);  
  24.     }  
  25.   
  26.     public void reset() {  
  27.         cache.clear();  
  28.     }  
  29. }  
在使用中你可以把CacheProvider放到Global或者Visit对象中。注意要使用同一个CacheProvider。

 

我在google code host上面建了一个probject地址是http://code.google.com/p/tfancomponents/ 有兴趣的同学可以看看, 这是一个maven项目。

   发表时间:2006-12-22  
这个idea不错,还有扩展的余地,保存的key最好不要用idPath,自己可以定义,例如定义一个cache名字为news,key应该为Home:news,这样我可以使用cacheProvider.update("Home:news")来更新,还可以写个annotation,例如我新建了个news,
@InjectCache("Home:news")
public CacheContent getHomeNewsCache();

public void save(){
   getNewsService().save(getNews());
   getHomeNewsCache().update();
}
这样可用性比较好,有兴趣的话,一起研究一下。
0 请登录后投票
   发表时间:2006-12-22  
tapestry 写道
这个idea不错,还有扩展的余地,保存的key最好不要用idPath,自己可以定义,例如定义一个cache名字为news,key应该为Home:news,这样我可以使用cacheProvider.update("Home:news")来更新,还可以写个annotation,例如我新建了个news,
@InjectCache("Home:news")
public CacheContent getHomeNewsCache();

public void save(){
   getNewsService().save(getNews());
   getHomeNewsCache().update();
}
这样可用性比较好,有兴趣的话,一起研究一下。


开始时我觉得这个cacheKey其实可以对用户不见的,用户不用关心cache的内容。其实这里的cache的内容也就是cache组件的body render之后的html source。但是ICacheProvider提供了removeCache这个方法。假如用idPath的对用户来说确实是不友好的。 指定cacheKey值的话,可读性也很高。这个想法不错。我会加入这个功能,多加个cacheKey属性,假如用户关心的话就指定, 不指定的话默认就是idpath。
<span cacheProvider="ognl:cacheProvider" cacheKey="literal:Home:news"   updateCondition="ognl:needUpdate">
//body  ....
</span>

@InjectCache("Home:news")
public CacheContent getHomeNewsCache();

你这个annotation的意思是获得cache的内容么? 我这里可能有多个cacheProvider, 而且我想用户可以方便的通过cacheProvider去拿到cache content。 再者我想用户肯定不会想去自己updateCache的内容,因为这个body是tapestry解析后的内容,我想用户关系的只是现在cache中的内容是不是stale的。我现在需不需要更新这个cache。 获得cache的内容交给tapestry来处理。 其实这个组件的目的也是主要是想cache页面的html source而已。

现在想要更新cache中的内容的话, 你只需要让updateCondition为true, 或者让对应的cache 内容为null 即可。

非常谢谢tapestry提供的建议,欢迎一起研究。
0 请登录后投票
   发表时间:2006-12-22  
我的CacheContent意思是封装一个对象,里边有个开关变量,设定是否需要update,当然还有cache的内容(这个才是你的cacheContent的意思),里边有个update方法,因为总感觉update应该是cache自己的事情,但实验了一下概念还是比较乱,先暂时放放。
使用起来应该有两种情况,一种是设置页面的update开关,一种是cacheProvider的removeCache。试想这样一个场景,ShowBlogs页面,你cache住了,然后用户添加了blog,除非他添加完后直接需要到ShowBlogs页面,可以调用ShowBlogs.setNeedUpdate(true),然后显示ShowBlogs页面来刷cache,但一般情况很可能不是跳到ShowBlogs页面,可能是用户自己的ManageBlogs页面,这个时候更新的唯一方法就是调用cacheProvider.removeCache了。
cacheProvider里加个updateCache方法吧,方法里调用removeCache,这样api清楚点。
0 请登录后投票
   发表时间:2006-12-22  
还有几点疑问,一个是tapestry本身就cache住了静态的模版文档,只需要动态内容添加进去然后生成页面,这个cache应该说是想将静态和动态内容cache住,不需要再生成了,既然是动态的必须是可被更新的,所以要给出个key来,好让人更新,老是调用页面去设置开关然后再显示好像不大方便,现在设置key的话就存在key的管理问题了,今天设定为news了,明天改了成newses了,好多地方就会更新不到了,设为enum或者常量都可以,一个数据字典的概念了,比如Home页面有3个cache了,分别是news,tags,links,后台更新数据的时候来看看哪里用了news了,更新下。这个扯远了,不过老是感觉还有改进的可能,但又暂时想不到。
0 请登录后投票
   发表时间:2006-12-22  
tapestry 写道
我的CacheContent意思是封装一个对象,里边有个开关变量,设定是否需要update,当然还有cache的内容(这个才是你的cacheContent的意思),里边有个update方法,因为总感觉update应该是cache自己的事情,但实验了一下概念还是比较乱,先暂时放放。
使用起来应该有两种情况,一种是设置页面的update开关,一种是cacheProvider的removeCache。试想这样一个场景,ShowBlogs页面,你cache住了,然后用户添加了blog,除非他添加完后直接需要到ShowBlogs页面,可以调用ShowBlogs.setNeedUpdate(true),然后显示ShowBlogs页面来刷cache,但一般情况很可能不是跳到ShowBlogs页面,可能是用户自己的ManageBlogs页面,这个时候更新的唯一方法就是调用cacheProvider.removeCache了。
cacheProvider里加个updateCache方法吧,方法里调用removeCache,这样api清楚点。


这里说说我的想法, 这个Cache主要是cache页面的,你想到的比较多,可能你忘了Cache组件中有个updateCondition 属性, 其实我是不太赞成ICacheProvider的removeCache这个方法的,这也是我开始想到使用idpath作为cacheKey的原因,用户不需要知道太多的东西。他仅仅需要知道什么时候我需要让tapestry重新生成一下Body的内容就行。 他也就需要实现updateConditon这个component方法。争对你说的这种场景, 我在updateCondition这个方法里面去判断数据库里面的最新帖子的时间和你生成cache时最新帖子的时间来比较(你可以保存在一个singtlon对象里面),如果时间更新了,return true就ok了。 其实我现在这个项目中也是这种情况,首页内容部分是由管理员upload的一个文件(owl file)生成的内容。这时候我在MiscUtils类里面有个checkOWLFileUpdated()方法去判断owl时候发生了变化, 也是使用file的lastmodfy time来判断的。 我在Cache组件中仅仅是非常方便的委派MiscUtils的那个方法而已。

你在上面也提到了主动的方式,就是在发表玩帖子的时候去设置updateCondition为true。其实你只要把这个值set到一个singlton类属性里面就ok了。 updateCondition方法就去check那个值吧。 我不是很赞成这种做法,因为他把一些非业务相关的逻辑加了进来,可能你会说我可以用aop方便的搞定这个。 但是我还是觉得判断cache是否要更新就让Cache组件来作罢(updateCondition)。
0 请登录后投票
   发表时间:2007-08-10  
不错!我想知道你为什么不直接使用oschache呢?
0 请登录后投票
   发表时间:2007-08-10  
koda 写道
不错!我想知道你为什么不直接使用oschache呢?


本来我是打算用oscache来实现了。 其实oscache自己带了filter来处理cache。  我想尽量把他做简单点。 容易理解。  其实你可以看到  我的这个组件需要提供个CacheProvider。 这个是个接口。 所以这样的话, 你想使用任何cache都是可以的。 只要用你现在用的cache实现CacheProvider就行了。  这样岂不是更灵活???
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics