2009年7月9日
JdonFramework的缓存设计
内嵌对象(Embedded Object)缓存设计
请看下面这段代码:
public class Category extends Model{
String Id;
Product product; //内嵌包含了一个Product对象
}
Category这个Model内嵌了Product这个Model,属于一种关联关系。
目前Jdon框架提供的缺省缓存是扁平式,不是嵌入式或树形的(当然也可以使用JbossCache等树形缓存替代),因此,一个ModelB对象(如Product)被嵌入到另外一个ModelA对象(如Category)中,那么这个ModelB对象随着ModelA对象被Jdon框架一起缓存。
假设实现ModelA已经在缓存中,如果客户端从缓存直接获取ModelB,缓存由于不知道ModelB被缓存到ModelA中(EJB3实体Bean中是通过Annotation标注字段),那么缓存可能告知客户端没有ModelB缓存。那么有可能客户端会再向缓存中加一个新的ModelB,这样同一个ID可能有两份ModelB,当客户端直接调用的ModelB进行其中字段更新,那么包含在ModelA中的ModelB可能未被更新(因为ModelA没有字段更新)。
有两种解决方案:
第一.最直接方式,通过手工清除ModelA缓存方式来更新,或者耐心等待ModelA缓存自动更新。手工清除缓存见下章。
注意:下面这种做法将也会导致不一致现象发生:
在DAO层读取数据库。生成ModelA时,直接读取数据库将ModelB充填。
第二.在进行ModelA和ModelB的相关操作服务设计时,就要注意保证这两种情况下ModelB指向的都是同一个。如下图:
为达到这个目的,只要在Service层和Dao层之间加一个缓存Decorator,服务层向Dao层调用的任何Model对象都首先经过缓存检查,缓存中保存的ModelA中的ModelB是一个只有ModelB主键的空对象,在服务层getModelA方法中,再对ModelA的ModelB进行充实,填满,根据ModelA中的ModelB的主键,首先再到缓存查询,如果有,则将缓存中ModelB充填到ModelA的ModelB中,这样上图目的就可以实现了。
相关实现可参考JiveJdon 3.0的代码,Forum/ForumMessage都属于这种情况。
Model缓存使用
Jdon框架通过两种方式使用Model缓存:
CRUD框架内部使用,如果你使用Jdon框架提供的CRUD功能,那么其已经内置Model缓存,而且会即时清除缓存。
通过CacheInterceptor缓存拦截器,如果你不使用Jdon框架的CRUD功能,缓存拦截器功能将激活,在向Service获取Model之前,首先查询当前缓存器中是否存在该Model,如果有从缓存中获取。当你的Model中数值更改后,必须注意自己需要手工清除该Model缓存,清除方法如下介绍。
Jdon框架除了提供单个Model缓存外,还在持久层Dao层提供了查询条件的缓存,例如如果你是根据某个字段按照如何排列等条件进行查询,这个查询条件将被缓存,这样,下次如果有相同查询条件,该查询条件将被提出,与其相关的满足查询条件一些结果(如符合条件总数等)也将被从缓存中提出,节省翻阅数据库的性能开销。
手工访问缓存
在一般情况下,前台表现层通过getService方法访问服务层一个服务,然后通过该服务Service获得一个Model,这种情况Jdon框架的缓存拦截器将自动首先从缓存读取。
但是,有时我们在服务层编码时,需要获得一个Model,在这种情况下,Jdon框架的缓存拦截器就不起作用,这时可能我们需要手工访问缓存。
因为所有服务类POJO都属于Jdon框架的容器内部组件,这实际是在容器内访问容器组件的问题。
使用com.jdon.container.finder. ContainerCallback,同时,该服务POJO类以ContainerCallback作为构造参数,当该POJO服务类注册到容器中时,容器的Ioc特性将会找到事先以及注册的ContainerCallback类。
通过ContainerCallback获得ContainerWrapper容器实例,然后通过ContainerWrapper下面方法:
public Object lookup(String name);
从容器中获得container.xml中注册的组件实例,这种方法每次调用获得的是同一个实例,相当于单例方式获得。
ModelManager modelManager =
(ModelManager)containerWrapper. lookup (“modelManager”);
其中“modelManager”字符串名称是从Jdon框架的jdonFramework.jar包中META-INF的container.xml中查询获知的。
获得ModelManager后,我们基本可以访问Model有关的功能安排,如ModelManager的getCache方法。
下节的手工清除缓存中,我们是通过WebAppUtil.getComponentInstance获得ModelManager实例,这是一种从容器外获得容器组件的方式,本节介绍从容器内获得容器组件的方式,这两种方式可根据我们实际需要灵活使用,关键是弄清除你需要在哪里触发组件调用?
手工清除缓存
注意:手工清除缓存不是必要的,因为缓存中对象是有存在周期的,这在Cache.xml中设置的,过一段时间缓存将自动清除那些超过配置时间不用的对象,这样你修改的数据将被从数据库重新加载。如果你等不及这些内在变化,可以手工处理:
有两种情况需要手工清除缓存,首先,在持久层的Dao类中,总是需要手工清除查询条件的缓存,只要在相应的增删改方法中调用PageIteratorSolver的clearCache方法既可。
如果你不实行这种缓存清除,那么你更改一个Model数据或新增一个新的Model数据,你在批量查询时,将看不到任何变化:Model数据没有被修改;新的Model没有出现在查询页面中。
其次,单个Model缓存在不使用Jdon框架的CRUD功能下也必须手工清除,如果你使用1.2.3以后版本,可以调用com.jdon.strutsutil.util. ModelUtil类的clearModelCache方法,该方法一般是Action中调用后台增删改服务之前被激活调用:
注意,手工清除Model缓存代码关键是:
modelManager.removeCache(keyValue);
keyValue是Model的主键值,例如User的主键userId值是”2356”,那么keyValue就是”2356”。简化代码如下:
//获得ModelManager实例
ModelManager modelManager = (ModelManager)
WebAppUtil.getComponentInstance(ComponentKeys.MODEL_MANAGER, request);
modelManager.removeCache(keyValue);
上面代码是在容器外访问获得ModelManager,使用上节容器内访问组件方式也可以获得ModelManager;前者适合在表现层使用;后者适合在服务层使用。
最后,介绍一下清除全部缓存的方式,调用ModelManager的clearCache方法(Jdon框架1.3以上版本),这样实际上将整个Jdon框架缓存全部清零。
前面两种清除缓存方式前提是首先获得ModelManager,特别是服务层需要清除缓存时,需要以容器内访问组件方式获得ModelManager,这只适合POJOService构成的服务层,如果我们使用EJB的Session Bean作为服务层实现,这时当前版本的Jdon框架容器不会在EJB容器中加载,因此,在Session Bean中无法访问到容器,无法获得ModelManager了,在这种情况下,可以通过设置Model的setModified属性为True,表示该Model已经修改更新,这样当表现层获取该Model时,Jdon框架缓存拦截器拦截时,发现该Model已经被修改,也就不会从缓存中获取。
Model还有另外一个方法setCacheble,当设置为false时,该Model将不会被保存到缓存中。如果你不希望某个Model被框架自动存入缓存,那么使用此功能,setCacheble和setModified区别是,前者一旦设置为真,相当于缓存失效,以后再也不能用缓存;而后者则是当前Model表示被修改过,这样当有任何再次(限一次)试图从缓存中读取这个Model时,都被会阻挡,从而可直接从数据库获得,然后再保存到缓存中,这样缓存中的Model数据就是新鲜的了。
明白Jdon框架setModified这个神奇作用,当你设置一个Model的setModified为真,那么你再读取缓存时,Jdon框架内部将忽略你这个读取,返回一个null;这样你就根据返回是否为空,再从数据库直接获得,获得后,别忘记再保存到缓存中,已覆盖前次修改的旧数据,保证以后每次从缓存中读取的都是新鲜数据。
=总结如下:手工缓存清除共有两种方式:
直接操作缓存,从缓存中清除;
如果无法操作到缓存体系,那么设置Model的setModified。
因为LForum使用的是freemaker,好像没看到页面缓存,好像也没关联。
但可以参考http://hain.javaeye.com/blog/152806
/**
*作者:张荣华
*日期:2007-9-30
**/
关于缓存的话题,在坛子里已经有很多讨论,简单的来说,如果一个应用中80%的时间内都在访问20%的数据,那么,这时候就应该使用缓存了。这个和长尾理论正好相悖,其实也不是相悖,只是不同的理论使用的场景不同。在80/20原则生效的地方,我们都应该考虑是否可以使用缓存。但即使是这样,缓存也有不同的用法,举个例子,一个网站的首页估计是被访问的次数最多的,我们可以考虑给首页做一个页面缓存,而如果在某个页面上,比如说javaeye的java版区只有前几个页面是访问最频繁的,(假设javaeye是使用hibernate,当然这只是假设,我们都知道javaeye是使用ror开发的)那么我们就可以考虑给java版区的record做二级缓存了,因为二级缓存中是按照对象的id来保存的,所以应该来说这前面几页使用的对象会一直存在于缓存之中(如何使用hibernate的二级缓存坛子上也有介绍)。由此可见不同的页面的缓存策略有可能有天壤之别。
本文的目的就是上面所讲的两种情况之一,页面缓存。毫无疑问,几乎所有的网站的首页都是访问率最高的,而首页上的数据来源又是非常广泛的,大多数来自不同的对象,而且有可能来自不同的db,所以给首页做缓存是一个不错的主意,那么主页的缓存策略是什么样子的呢,我认为应该是某个固定时间之内不变的,比如说2分钟更新一次。那么这个缓存应该做在什么地方呢,让我们来看一下,假设您的应用的结构是page-filter-action-service-dao-db,这个过程中的-的地方都是可以做缓存的地方,根据页面缓存的特征,应该把页面缓存做到尽量靠近客户的地方,就是在page和filter之间,这样的优点就是第一个用户请求之后,页面被缓存,第二个用户再来请求的时候,走到filter这个请求就结束了,无需再走后面的action-service-dao-db。带来的好处是服务器压力的减低和客户段页面响应速度的加快。
那么我们来看一下如何使用ehcache做到这一点。
在使用ehcache的页面缓存之前,我们必须要了解ehcache的几个概念,
1 timeToIdleSeconds,多长时间不访问该缓存,那么ehcache就会清除该缓存。
2 timeToLiveSeconds,缓存的存活时间,从开始创建的时间算起。
看到这里,我们知道,首页的页面缓存的存活时间,我们定的是2分钟,那么也就是说我们的timeToLiveSeconds应该设置为120,同时我们的timeToIdleSeconds最好也设置为2分钟,或者大于2分钟。我们来看一下下面这个配置,这个配置片段应该放到ehcache.xml中:
<cache name="SimplePageCachingFilter"
maxElementsInMemory="10"
maxElementsOnDisk="10"
eternal="false"
overflowToDisk="true"
diskSpoolBufferSizeMB="20"
timeToIdleSeconds="10"
timeToLiveSeconds="10"
memoryStoreEvictionPolicy="LFU"
/>
SimplePageCachingFilter是缓存的名字,maxElementsInMemory表示内存中SimplePageCachingFilter缓存中元素的最大数量为10,maxElementsOnDisk是指持久化该缓存的元素到硬盘上的最大数量也为10(),eternal=false意味着该缓存会死亡。overflowToDisk=true意思是表示当缓存中元素的数量超过限制时,就把这些元素持久化到硬盘,如果overflowToDisk是false,那么maxElementsOnDisk的设置就没有什么意义了。memoryStoreEvictionPolicy=LFU是指按照缓存的hit值来清除,也就是说缓存满了之后,新的对象需要缓存时,将会将缓存中hit值最小的对象清除出缓存,给新的对象腾出地方来了(文章最后有ehcache中自带的3种缓存清空策略的介绍)。
接着我们来看一下SimplePageCachingFilter的配置,
<filter>
<filter-name>indexCacheFilterfilter-name>
<filter-class>
net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter
filter-class>
filter>
<filter-mapping>
<filter-name>indexCacheFilterfilter-name>
<url-pattern>*index.actionurl-pattern>
filter-mapping>
就只需要这么多步骤,我们就可以给某个页面做一个缓存的,把上面这段配置放到你的web.xml中,那么当你打开首页的时候,你会发现,2分钟才会有一堆sql语句出现在控制台上。当然你也可以调成5分钟,总之一切都在控制中。
好了,缓存整个页面看上去是非常的简单,甚至都不需要写一行代码,只需要几行配置就行了,够简单吧,虽然看上去简单,但是事实上内部实现却不简单哦,有兴趣的话,大家可以看看SimplePageCachingFilter继承体系的源代码。
上面的配置针对的情况是缓存首页的全部,如果你只想缓存首页的部分内容时,你需要使用SimplePageFragmentCachingFilter这个filter。我们看一下如下片断:
<filter>
<filter-name>indexCacheFilterfilter-name>
<filter-class>
net.sf.ehcache.constructs.web.filter.SimplePageFragmentCachingFilter
filter-class>
filter>
<filter-mapping>
<filter-name>indexCacheFilterfilter-name>
<url-pattern>*/index_right.jspurl-pattern>
filter-mapping>
这个jsp需要被jsp:include到其他页面,这样就做到的局部页面的缓存。这一点貌似没有oscache的tag好用。
事实上在cachefilter中还有一个特性,就是gzip,也就是说缓存中的元素是被压缩过的,如果客户浏览器支持压缩的话,filter会直接返回压缩过的流,这样节省了带宽,把解压的工作交给了客户浏览器,如果客户的浏览器不支持gzip,那么filter会把缓存的元素拿出来解压后再返回给客户浏览器(大多数爬虫是不支持gzip的,所以filter也会解压后再返回流),这样做的优点是节省带宽,缺点就是增加了客户浏览器的负担(但是我觉得对当代的计算机而言,这个负担微乎其微)。
好了,如果你的页面正好也需要用到页面缓存,不防可以考虑一下ehcache,因为它实在是非常简单,而且易用。
总结:ehcache是一个非常轻量级的缓存实现,而且从1.2之后就支持了集群,目前的最新版本是1.3,而且是hibernate默认的缓存provider。虽然本文是介绍的是ehcache对页面缓存的支持,但是ehcache的功能远不止如此,当然要使用好缓存,对JEE中缓存的原理,使用范围,适用场景等等都需要有比较深刻的理解,这样才能用好缓存,用对缓存。
最后复习一下ehcache中缓存的3种清空策略:
1 FIFO,first in first out,这个是大家最熟的,先进先出,不多讲了
2 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
2 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
由于不大熟悉SpringSide3,找了半天没找到配置hiberante的xml文件,最后搜索一下,出来了:
在E:\MyEclipseWorkplace\LForum\webapp\WEB-INF\config\applicationContext.xml里面有一段:
<!-- Hibernate配置 -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="namingStrategy">
<bean class="org.hibernate.cfg.ImprovedNamingStrategy" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">DB_DIALECT</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider
</prop>
<prop key="hibernate.cache.provider_configuration_file_resource_path">/ehcache-hibernate.xml</prop>
</props>
</property>
<property name="packagesToScan" value="com.javaeye.lonlysky.lforum.entity*,com.javaeye.lonlysky.lforum.entity.*" />
</bean>那么如何更新缓存?
缓存在Hibernate中主要有三个方面:一级缓存、二级缓存和查询缓存;一级缓存在Hibernate中对应的即为session范围的缓存,
也就是当 session关闭时缓存即被清除,一级缓存在Hibernate中是不可配置的部分;二级缓存在Hibernate中对应的即为 SessionFactory范围的缓存,
通常来讲SessionFactory的生命周期和应用的生命周期相同,所以可以看成是进程缓存或集群缓存,
二级缓存在Hibernate中是可以配置的:
可以通过class-cache配置类粒度级别的缓存(class-cache在class中数据发生任何变化的情况下自动更新),
同时也可通过collection-cache配置集合粒度级别的缓存(collection-cache仅在 collection中增加了元素或者删除了元素的情况下才自动更新,
也就是当collection中元素发生值的变化的情况下它是不会自动更新的),缓存自然会带来并发的访问问题,这个时候相应的就要根据应用来设置缓存所采用的事务隔离级别,
和数据库的事务隔离级别概念基本一样,没什么多介绍的, ^_^;查询缓存在Hibernate同样是可配置的,默认是关闭的,
可以通过设置cache.use_ query_cache为true来打开查询缓存。
所以在实体里面:
@Entity
@Table(name = "forums")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Forums implements java.io.Serializable {
.......
}CacheConcurrencyStrategy.READ_WRITE,读写模式在更新缓存的时候会把缓存里面的数据换成一个锁,
其它事务如果去取相应的缓存数据,发现被锁了,直接就去数据库查询;
这里启用的是类粒度级别的缓存。
那么为什么里面会有手工removeCache的操作:
主要是那些缓存不是实体本身,而是其他一些东西或实体列表,如ForumListBoxOptions,
ForumList等。
package com.javaeye.lonlysky.lforum.service.admin;
import java.util.List;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springside.modules.orm.hibernate.SimpleHibernateTemplate;
import com.javaeye.lonlysky.lforum.GlobalsKeys;
import com.javaeye.lonlysky.lforum.cache.LForumCache;
import com.javaeye.lonlysky.lforum.comm.utils.Utils;
import com.javaeye.lonlysky.lforum.entity.forum.Admingroups;
import com.javaeye.lonlysky.lforum.entity.forum.Forums;
import com.javaeye.lonlysky.lforum.entity.forum.Moderators;
import com.javaeye.lonlysky.lforum.entity.forum.Users;
import com.javaeye.lonlysky.lforum.service.ForumManager;
import com.javaeye.lonlysky.lforum.service.UserManager;
/**
* 后台论坛版块管理类
*
* @author 黄磊
*
*/
@Service
@Transactional
public class AdminForumManager {
private static final Logger logger = LoggerFactory.getLogger(AdminForumManager.class);
private SimpleHibernateTemplate<Forums, Integer> forumDAO;
private SimpleHibernateTemplate<Moderators, Integer> moderatorDAO;
@Autowired
private ForumManager forumManager;
@Autowired
private UserManager userManager;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
forumDAO = new SimpleHibernateTemplate<Forums, Integer>(sessionFactory, Forums.class);
moderatorDAO = new SimpleHibernateTemplate<Moderators, Integer>(sessionFactory, Moderators.class);
}
/**
* 设置版块列表中层数(layer)和父列表(parentidlist)字段
*/
public void setForumslayer() {
List<Forums> forumList = forumManager.getForumList();
for (Forums forum : forumList) {
int layer = 0;
String parentidlist = "";
int parentid = forum.getForums().getFid();
//如果是(分类)顶层则直接更新数据库
if (parentid == 0) {
// forum.setLayer(layer);
// forum.setParentidlist("0");
// forumManager.updateForum(forum);
System.out.println("顶层直接更新数据库:" + forum.getFid());
forumDAO
.createQuery("update Forums set layer=?,parentidlist=? where fid=?", layer, "0", forum.getFid())
.executeUpdate();
continue;
}
do { //更新子版块的层数(layer)和父列表(parentidlist)字段
int tmp = parentid;
parentid = Utils.null2Int(forumDAO.findUnique("select forums.fid from Forums where fid=?", parentid));
layer++;
if (parentid != 0) {
parentidlist = tmp + "," + parentidlist;
} else {
parentidlist = tmp + "," + parentidlist;
// forum.setLayer(layer);
// forum.setParentidlist(parentidlist.substring(0, parentidlist.length() - 1));
// forumManager.updateForum(forum);
System.out.println("更新子版块:" + forum.getFid() + ",层数:" + layer + ",上级ID列表:"
+ parentidlist.substring(0, parentidlist.length() - 1));
forumDAO.createQuery("update Forums set layer=?,parentidlist=? where fid=?", layer,
parentidlist.substring(0, parentidlist.length() - 1), forum.getFid()).executeUpdate();
break;
}
} while (true);
}
if (logger.isDebugEnabled()) {
logger.debug("设置版块列表中层数(layer)和父列表(parentidlist)字段");
}
}
public static String childNode = "0";
/**
* 递归所有子节点并返回字符串
* @param correntfid 当前
* @return 子版块的集合,格式:1,2,3,4,
*/
@SuppressWarnings("unchecked")
public String findChildNode(int correntfid) {
synchronized (childNode) {
List<Object> list = forumDAO.find("select fid from Forums where forums.fid=? order by displayorder asc",
correntfid);
childNode = childNode + "," + correntfid;
if (list.size() > 0) {
//有子节点
for (Object object : list) {
findChildNode(Utils.null2Int(object));
}
}
if (logger.isDebugEnabled()) {
logger.debug("递归所有子节点{}", childNode);
}
return childNode;
}
}
/**
* 设置论坛字版数和显示顺序
*/
public void setForumsSubForumCountAndDispalyorder() {
if (logger.isDebugEnabled()) {
logger.debug("设置论坛字版数和显示顺序");
}
List<Forums> forumList = forumManager.getForumList();
for (Forums forum : forumList) {
int subcount = forumDAO.find("select fid from Forums where forums.fid=?", forum.getFid()).size();
// forumDAO.createQuery("update Forums set subforumcount=? where fid=?", subcount, forum.getFid())
// .executeUpdate();
forum.setSubforumcount(subcount);
forumManager.updateForum(forum);
System.out.println("更新子版块数量为:" + forum.getSubforumcount() + ",板块:" + forum.getFid());
}
if (forumList.size() == 1)
return;
int displayorder = 1;
String fidlist;
for (Forums forum : forumManager.getForumList("forums.fid=0")) {
if (forum.getForums().getFid() == 0) {
childNode = "0";
fidlist = ("," + findChildNode(forum.getFid())).replace(",0,", "");
for (String fidstr : fidlist.split(",")) {
// forumDAO.createQuery("update Forums set displayorder=? where fid=?", displayorder,
// Utils.null2Int(fidstr)).executeUpdate();
Forums forums = forumDAO.get(Utils.null2Int(fidstr));
forums.setDisplayorder(displayorder);
forumManager.updateForum(forums);
System.out.println("更新板块:" + forums.getFid() + "显示顺序:" + forums.getDisplayorder());
displayorder++;
}
}
}
}
/**
* 移动论坛版块
* @param currentfid 当前论坛版块id
* @param targetfid 目标论坛版块id
* @param isaschildnode 是否作为子论坛移动
* @return
*/
@Transactional(readOnly = false)
public void moveForumsPos(int currentfid, int targetfid, boolean isaschildnode) {
if (logger.isDebugEnabled()) {
logger.debug("移动板块,当前板块:{},目标板块:{},是否作为子论坛:" + isaschildnode, currentfid, targetfid);
}
//取得当前论坛版块的信息
Forums currentForum = forumDAO.get(currentfid);
//取得目标论坛版块的信息
Forums targetForum = forumDAO.get(targetfid);
//当前论坛版块带子版块时
if (forumDAO.find("select fid from Forums where forums.fid=?", currentfid).size() > 0) {
System.out.println("当前论坛版块带子版块");
if (isaschildnode) { //作为论坛子版块插入
//让位于当前论坛版块(分类)显示顺序之后的论坛版块全部加1(为新加入的论坛版块让位结果)
forumDAO.createQuery("update Forums set displayorder=displayorder+1 where displayorder>=?",
(targetForum.getDisplayorder() + 1)).executeUpdate();
//更新当前论坛版块的相关信息
currentForum.setForums(targetForum);
currentForum.setDisplayorder(targetForum.getDisplayorder() + 1);
// forumDAO.createQuery("update Forums set forums.fid=?,displayorder=? where fid=?", targetForum.getFid(),
// targetForum.getDisplayorder() + 1, currentfid).executeUpdate();
} else { //作为同级论坛版块,在目标论坛版块之前插入
//让位于包括当前论坛版块显示顺序之后的论坛版块全部加1(为新加入的论坛版块让位结果)
forumDAO.createQuery("update Forums set displayorder=displayorder+1 where displayorder>=? or fid=?",
targetForum.getDisplayorder(), targetForum.getFid()).executeUpdate();
//更新当前论坛版块的相关信息
currentForum.setForums(targetForum.getForums());
currentForum.setDisplayorder(targetForum.getDisplayorder());
// forumDAO.createQuery("update Forums set forums.fid=?,displayorder=? where fid=?",
// targetForum.getForums().getFid(), targetForum.getDisplayorder(), currentfid).executeUpdate();
}
//更新由于上述操作所影响的版块数和帖子数
if (currentForum.getTopics_1() != 0 && currentForum.getTopics_1() > 0
&& (currentForum.getPosts() != 0 && currentForum.getPosts() > 0)) {
if (!currentForum.getParentidlist().trim().equals("")) {
forumDAO.createQuery(
"update Forums set topics_1=topics_1-" + currentForum.getTopics_1() + ",posts=posts-"
+ currentForum.getPosts() + " where fid in("
+ currentForum.getParentidlist().trim() + ")").executeUpdate();
}
if (!targetForum.getParentidlist().trim().equals("")) {
forumDAO.createQuery(
"update Forums set topics_1=topics_1+" + currentForum.getTopics_1() + ",posts=posts+"
+ currentForum.getPosts() + " where fid in(" + targetForum.getParentidlist().trim()
+ ")").executeUpdate();
}
}
} else { //当前论坛版块不带子版
System.out.println("当前论坛版块不带子版");
//设置旧的父一级的子论坛数
forumDAO.createQuery("update Forums set subforumcount=subforumcount-1 where fid=?",
currentForum.getForums().getFid()).executeUpdate();
//让位于当前节点显示顺序之后的节点全部减1 [起到删除节点的效果]
if (isaschildnode) { //作为子论坛版块插入
//更新相应的被影响的版块数和帖子数
if ((currentForum.getTopics_1() != 0) && (currentForum.getTopics_1() > 0)
&& (currentForum.getPosts() != 0) && (currentForum.getPosts() > 0)) {
forumDAO.createQuery(
"update Forums set topics_1=topics_1-" + currentForum.getTopics_1() + ",posts=posts-"
+ currentForum.getPosts() + " where fid in("
+ currentForum.getParentidlist().trim() + ")").executeUpdate();
if (!targetForum.getParentidlist().trim().equals("0")) {
forumDAO.createQuery(
"update Forums set topics_1=topics_1+" + currentForum.getTopics_1() + ",posts=posts+"
+ currentForum.getPosts() + " where fid in("
+ targetForum.getParentidlist().trim() + "," + targetfid + ")").executeUpdate();
}
}
//让位于当前论坛版块显示顺序之后的论坛版块全部加1(为新加入的论坛版块让位结果)
forumDAO.createQuery("update Forums set displayorder=displayorder+1 where displayorder>=?",
targetForum.getDisplayorder() + 1).executeUpdate();
//设置新的父一级的子论坛数
targetForum.setSubforumcount(targetForum.getSubforumcount() + 1);
// forumDAO.createQuery("update Forums set subforumcount=subforumcount+1 where fid=?", targetfid)
// .executeUpdate();
String parentidlist = null;
if (targetForum.getParentidlist().trim().equals("0")) {
parentidlist = targetfid + "";
} else {
parentidlist = targetForum.getParentidlist().trim() + "," + targetfid;
}
//更新当前论坛版块的相关信息
currentForum.setForums(targetForum);
currentForum.setLayer(targetForum.getLayer() + 1);
currentForum.setPathlist(targetForum.getPathlist().trim() + "<a href=\"showforum.action?forumid="
+ currentfid + "\">" + currentForum.getName().trim().replace("'", "''") + "</a>");
currentForum.setParentidlist(parentidlist);
currentForum.setDisplayorder(targetForum.getDisplayorder() + 1);
System.out.println("作为子论坛版块插入:" + currentForum.getLayer());
// forumDAO.createQuery(
// "update Forums set forums.fid=?,layer=?,pathlist=?,parentidlist=?,displayorder=? where fid=?",
// targetForum.getFid(),
// targetForum.getLayer() + 1,
// targetForum.getPathlist().trim() + "<a href=\"showforum.action?forumid=" + currentfid + "\">"
// + currentForum.getName().trim().replace("'", "''") + "</a>", parentidlist,
// targetForum.getDisplayorder() + 1, currentfid).executeUpdate();
} else { //作为同级论坛版块,在目标论坛版块之前插入
//更新相应的被影响的版块数和帖子数
if ((currentForum.getTopics_1() != 0) && (currentForum.getTopics_1() > 0)
&& (currentForum.getPosts() != 0) && (currentForum.getPosts() > 0)) {
forumDAO.createQuery(
"update Forums set topics_1=topics_1-" + currentForum.getTopics_1() + ",posts=posts-"
+ currentForum.getPosts() + " where fid in("
+ currentForum.getParentidlist().trim() + ")").executeUpdate();
forumDAO.createQuery(
"update Forums set topics_1=topics_1+" + currentForum.getTopics_1() + ",posts=posts+"
+ currentForum.getPosts() + " where fid in(" + targetForum.getParentidlist().trim()
+ ")").executeUpdate();
}
//让位于包括当前论坛版块显示顺序之后的论坛版块全部加1(为新加入的论坛版块让位结果)
forumDAO.createQuery("update Forums set displayorder=displayorder+1 where displayorder>=? or fid=?",
targetForum.getDisplayorder() + 1, targetForum.getFid()).executeUpdate();
//设置新的父一级的子论坛数
forumDAO.createQuery("update Forums set subforumcount=subforumcount+1 where fid=?",
targetForum.getForums().getFid()).executeUpdate();
String parentpathlist = Utils.null2String(forumDAO.findUnique(
"select pathlist from Forums where fid=?", targetForum.getForums().getFid()));
//更新当前论坛版块的相关信息
currentForum.setForums(targetForum.getForums());
currentForum.setLayer(targetForum.getLayer());
currentForum.setPathlist(parentpathlist + "<a href=\"showforum.action?forumid=" + currentfid + "\">"
+ currentForum.getName().trim() + "</a>");
currentForum.setParentidlist(targetForum.getParentidlist().trim());
currentForum.setDisplayorder(targetForum.getDisplayorder());
// forumDAO.createQuery(
// "update Forums set forums.fid=?,layer=?,pathlist=?,parentidlist=?,displayorder=? where fid=?",
// targetForum.getForums().getFid(),
// targetForum.getLayer(),
// parentpathlist + "<a href=\"showforum.action?forumid=" + currentfid + "\">"
// + currentForum.getName().trim() + "</a>", targetForum.getParentidlist().trim(),
// targetForum.getDisplayorder(), currentfid).executeUpdate();
}
}
forumManager.updateForum(currentForum);
forumManager.updateForum(targetForum);
}
/**
* 移动论坛版块
* @param currentfid 当前论坛版块id
* @param targetfid 目标论坛版块id
* @param isaschildnode 是否作为子论坛移动
* @return
*/
public boolean movingForumsPos(int currentfid, int targetfid, boolean isaschildnode) {
moveForumsPos(currentfid, targetfid, isaschildnode);
setForumslayer();
setForumsSubForumCountAndDispalyorder();
setForumsPathList();
LForumCache.getInstance().removeCache("ForumListBoxOptions");
LForumCache.getInstance().removeCache("ForumList");
return true;
}
/**
* 设置版块列表中论坛路径(pathlist)字段
*/
@Transactional(readOnly = false)
public void setForumsPathList() {
List<Forums> forumList = forumManager.getForumList();
for (Forums forum : forumList) {
String pathlist = "";
if (forum.getParentidlist().trim().equals("0")) {
pathlist = "<a href=\"showforum.action?forumid=" + forum.getFid() + "\">" + forum.getName().trim()
+ "</a>";
} else {
for (String parentid : forum.getParentidlist().trim().split(",")) {
if (!parentid.trim().equals("")) {
Forums tmpForums = forumDAO.get(Utils.null2Int(parentid));
if (tmpForums != null) {
pathlist += "<a href=\"showforum.action?forumid=" + tmpForums.getFid() + "\">"
+ tmpForums.getName().trim() + "</a>";
}
}
}
pathlist += "<a href=\"showforum.action?forumid=" + forum.getFid() + "\">" + forum.getName().trim()
+ "</a>";
}
forum.setPathlist(pathlist);
forumManager.updateForum(forum);
if (logger.isDebugEnabled()) {
logger.debug("设置论坛 {} 路径为 {}", forum.getFid(), forum.getPathlist());
}
}
}
/**
* 获得用于树形的板块列表
* @return
*/
@SuppressWarnings("unchecked")
public List<Object[]> getForumTree() {
return forumDAO.find("select fid,name from Forums");
}
/**
* 在当前节点之后加入同级论坛时的displayorder字段值
* @param minDisplayOrder
*/
public void updateForumsDisplayOrder(int minDisplayOrder) {
forumDAO.createQuery("update Forums set displayorder=displayorder+1 where displayorder>?", minDisplayOrder);
}
/**
* 向版块列表中插入新的版块信息
* @param foruminfo
* @return
*/
public String insertForumsInf(Forums foruminfo) {
forumDAO.save(foruminfo);
setForumsPathList();
LForumCache.getInstance().removeCache("ForumListBoxOptions");
LForumCache.getInstance().removeCache("ForumList");
LForumCache.getInstance().removeCache("HotForumList");
LForumCache.getInstance().removeCache("ForumHotTopicList");
LForumCache.getInstance().removeCache("ForumNewTopicList");
if (logger.isDebugEnabled()) {
logger.debug("添加新的论坛板块 {} 成功", foruminfo.getFid());
}
return setForumsModerators(foruminfo.getFid(), foruminfo.getForumfields().getModerators(), foruminfo
.getInheritedmod());
}
/**
* 设置指定论坛版块版主
* @param fid 指定的论坛版块id
* @param moderators 相关要设置的版主名称(注:用","号分割)
* @param inheritedmod 是否使用继承选项 1为使用 0为不使用
* @return
*/
public String setForumsModerators(int fid, String moderators, int inheritedmod) {
deleteModeratorByFid(fid);
//使用继承机制时
if (inheritedmod == 1) {
int parentid = fid;
String parendidlist = "-1";
while (true) {
parentid = Utils.null2Int(forumDAO.findUnique(
"select forums.fid from Forums where inheritedmod=1 and fid=?", fid));
if (parentid == -1) {
break;
}
if (parentid == 0) {
break;
}
parendidlist = parendidlist + "," + parentid;
}
int count = 1;
for (Users user : getUidModeratorByFid(parendidlist)) {
addModerator(user, fid, count, 1);
count++;
}
}
insertForumsModerators(fid, moderators, 1, 0);
return updateUserInfoWithModerator(moderators);
}
/**
* 更新当前已设置为指定版块版主的相关用户信息
* @param moderators 相关要设置的版主名称(注:用","号分割)
* @return 返回不存在用户的字符串
*/
public String updateUserInfoWithModerator(String moderators) {
moderators = moderators == null ? "" : moderators;
String usernamenoexsit = "";
Object obj = new Object();
for (String moderator : moderators.split(",")) {
if (!moderator.equals("")) {
//当用户名是系统保留的用户名,请您输入其它的用户名
if (GlobalsKeys.SYSTEM_USERNAME.equals(moderator)) {
continue;
}
obj = getModeratorInfo(moderator);
if (obj != null) {
Object[] objects = (Object[]) obj;
int groupid = Utils.null2Int(objects[1]);
if ((groupid <= 3) && (groupid > 0))
continue; //当为管理员,超级版主,版主时
else {
int radminid = Utils.null2Int(forumDAO.findUnique(
"select admingroups.admingid from Usergroups where groupid=?", groupid));
if (radminid <= 0)
setModerator(moderator);
else
continue;
}
} else {
usernamenoexsit = usernamenoexsit + moderator + ",";
}
}
}
AdminCacheManager.reSetModeratorList();
return usernamenoexsit;
}
public void setModerator(String moderator) {
forumDAO.createQuery("update Users set admingroups.admingid=3,usergroups.groupid=3 where username=?",
moderator.trim()).executeUpdate();
forumDAO.createQuery("update Online set admingroups.admingid=3,usergroups.groupid=3 where username=?",
moderator.trim()).executeUpdate();
}
/**
* 获取版主信息
* @param moderator 版主名称
* @return
*/
public Object getModeratorInfo(String moderator) {
return moderatorDAO
.createQuery(
"select uid,usergroups.groupid from Users where usergroups.groupid<>7 and usergroups.groupid<>8 and username=?",
moderator).setMaxResults(1).uniqueResult();
}
/**
* 向版主列表中插入相关的版主信息
* @param fid 向版主列表中插入相关的版主信息
* @param moderators 相关要设置的版主名称(注:用","号分割)
* @param displayorder 显示顺序
* @param inherited 是否使用继承机制
*/
@Transactional(readOnly = false)
public void insertForumsModerators(int fid, String moderators, int displayorder, int inherited) {
moderators = moderators == null ? "" : moderators;
int count = displayorder;
Forums forums = forumDAO.get(fid);
//数据库中存在的用户
String usernamelist = "";
//清除已有论坛的版主设置
for (String username : moderators.split(",")) {
if (!username.trim().equals("")) {
Object object = forumDAO.createQuery(
"select uid from Users where usergroups.groupid<>7 and usergroups.groupid<>8 and username=?",
username).setMaxResults(1).uniqueResult();
//先取出当前节点的信息
if (object != null) {
Moderators moderator = new Moderators();
moderator.setDisplayorder(count);
moderator.setForums(forums);
moderator.setInherited(inherited);
moderator.setUsers(new Users(Utils.null2Int(object)));
moderatorDAO.save(moderator);
usernamelist = usernamelist + username.trim() + ",";
count++;
}
}
}
if (!usernamelist.equals("")) {
forums.getForumfields().setModerators(moderators);
} else {
forums.getForumfields().setModerators("");
}
forumDAO.save(forums);
AdminCacheManager.reSetModeratorList();
}
/**
* 添加论坛版主
* @param users 用户
* @param fid 板块ID
* @param displayorder 显示顺序
* @param inherited 是否使用继承选项 1为使用 0为不使用
*/
public void addModerator(Users users, int fid, int displayorder, int inherited) {
Forums forums = new Forums();
forums.setFid(fid);
Moderators moderators = new Moderators();
moderators.setDisplayorder(displayorder);
moderators.setForums(forums);
moderators.setInherited(inherited);
moderators.setUsers(users);
moderatorDAO.save(moderators);
if (logger.isDebugEnabled()) {
logger.debug("添加论坛 {} 版主 {} 成功", fid, moderators.getId());
}
}
/**
* 获取指定板块版主列表
* @param fidlist
* @return
*/
@SuppressWarnings("unchecked")
public List<Users> getUidModeratorByFid(String fidlist) {
if (logger.isDebugEnabled()) {
logger.debug("获取指定板块{}版主列表", fidlist);
}
return forumDAO.find("select distinct users from Moderators where forums.fid in(" + fidlist + ")");
}
/**
* 清除已有论坛的版主设置
*
* @param fid
*/
public void deleteModeratorByFid(int fid) {
forumDAO.createQuery("delete from Moderators where forums.fid=?", fid).executeUpdate();
}
/**
* 检测指定板块ID下是否有子板块
* @param fid 板块ID
* @return 是否
*/
public boolean isExistSubForum(int fid) {
int count = Utils.null2Int(forumDAO.findUnique("select count(fid) from Forums where forums.fid=?", fid), 0);
return count > 0 ? true : false;
}
/**
* 删除指定fid的论坛版块
* @param fid 要删除的论坛版块的fid
* @return
*/
@Transactional(readOnly = false)
public boolean deleteForumsByFid(int fid) {
if (isExistSubForum(fid)) {
return false;
}
//先取出当前节点的信息
Forums forums = forumDAO.get(fid);
//调整在当前节点排序位置之后的节点,做减1操作
forumDAO.createQuery("update Forums set displayorder=displayorder-1 where displayorder>?",
forums.getDisplayorder()).executeUpdate();
//修改父结点中的子论坛个数
forumDAO
.createQuery("update Forums set subforumcount=subforumcount-1 where fid=?", forums.getForums().getFid())
.executeUpdate();
//删除相关投票的信息
forumDAO.createQuery("delete from Polls where topics.tid in(select tid from Topics where forums.fid=?)", fid)
.executeUpdate();
//删除帖子附件表中的信息
forumDAO
.createQuery(
"delete from Attachments where topics.tid in(select tid from Topics where forums.fid=? or postid.pid in(select pid from Posts where forums.fid=?))",
fid, fid).executeUpdate();
//删除相关帖子
forumDAO.createQuery("delete from Posts where forums.fid=?", fid).executeUpdate();
//删除相关主题
forumDAO.createQuery("delete from Topics where forums.fid=?", fid).executeUpdate();
//删除当前节点
forumDAO.delete(forums);
//删除版主列表中的相关信息
forumDAO.createQuery("delete from Moderators where forums.fid=?", fid).executeUpdate();
LForumCache.getInstance().removeCache("ForumListBoxOptions");
LForumCache.getInstance().removeCache("ForumList");
if (logger.isDebugEnabled()) {
logger.debug("删除论坛板块 {} 成功", fid);
}
return true;
}
/**
* 检测指定论坛是否为顶级论坛
* @param fid
* @return
*/
public int getTopForum(int fid) {
return Utils.null2Int(forumDAO.createQuery("select fid from Forums where forums.fid=0 and fid=?", fid), -1);
}
/**
* 合并版块
* @param sourcefid 合并版块
* @param targetfid 目标论坛版块
* @return
*/
@Transactional(readOnly = false)
public boolean combinationForums(int sourcefid, int targetfid) {
if (isExistSubForum(sourcefid)) {
return false;
} else {
childNode = "0";
String fidlist = ("," + findChildNode(targetfid)).replace(",0,", "");
//更新帖子与主题的信息
forumDAO.createQuery("update Topics set forums.fid=? where forums.fid=?", targetfid, sourcefid)
.executeUpdate();
//要更新目标论坛的主题数
int totaltopics = Utils.null2Int(forumDAO.findUnique("select count(tid) from Topics where forums.fid in ("
+ fidlist + ")"), 0);
int totalposts = 0;
forumDAO.createQuery("update Posts set forums.fid=? where forums.fid=?", targetfid, sourcefid)
.executeUpdate();
//要更新目标论坛的帖子数
totalposts = totalposts
+ Utils.null2Int(forumDAO.findUnique("select count(pid) from Posts where forums.fid in (" + fidlist
+ ")"), 0);
// 获取论坛信息
Forums targetForum = forumDAO.get(targetfid);
Forums sourceForum = forumDAO.get(sourcefid);
targetForum.setTopics_1(totaltopics);
targetForum.setPosts(totalposts);
//调整在当前节点排序位置之后的节点,做减1操作
forumDAO.createQuery("update Forums set displayorder=displayorder-1 where displayorder>?",
sourceForum.getDisplayorder()).executeUpdate();
//修改父结点中的子论坛个数
forumDAO.createQuery("update Forums set subforumcount=subforumcount-1 where fid=?",
sourceForum.getForums().getFid()).executeUpdate();
//删除源论坛版块
forumDAO.delete(sourceForum);
forumManager.updateForum(targetForum);
LForumCache.getInstance().removeCache("ForumListBoxOptions");
LForumCache.getInstance().removeCache("ForumList");
if (logger.isDebugEnabled()) {
logger.debug("合并论坛 {} 到目标论坛 {}", sourcefid, targetfid);
}
return true;
}
}
/**
* 获取当前UID是否为非指定板块的版主
* @param currentfid 板块
* @param uid UID
* @return 不是则返回-1
*/
public int getUidInModeratorsByUid(int currentfid, int uid) {
return Utils.null2Int(forumDAO.createQuery(
"select users.uid from Moderators where forums.fid<>? and users.uid=?", currentfid, uid)
.setMaxResults(1).uniqueResult());
}
/**
* 对比指定的论坛版块的新老信息,将作出相应的调整
* @param oldmoderators 老版主名称(注:用","号分割)
* @param newmoderators 新版主名称(注:用","号分割)
* @param currentfid 当前论坛版块的fid
*/
public void compareOldAndNewModerator(String oldmoderators, String newmoderators, int currentfid) {
if (Utils.null2String(oldmoderators).equals("")) {
return;
}
//在新的版主名单中查找老的版主是否存在
for (String oldmoderator : oldmoderators.split(",")) {
if ((!oldmoderator.equals("")) && ("," + newmoderators + ",").indexOf("," + oldmoderator + ",") < 0) //当不存在,则表示当前老的版主已被删除,则执行删除当前老版主
{
Object[] objects = userManager.getUidAdminIdByUsername(oldmoderator);
if (objects != null) //当前用户存在
{
int uid = Utils.null2Int(objects[0]);
int radminid = Utils.null2Int(objects[1]);
//在其他版块未曾设置为版主 ,则不作任何处理
if ((getUidInModeratorsByUid(currentfid, uid) != -1) && (radminid != 1)) {
Users userinfo = userManager.getUserInfo(uid);
forumDAO.createQuery("update Online] SET usergroups.groupid=? where users.uid=?", userinfo
.getUsergroups().getGroupid(), uid);
Admingroups admingroups = new Admingroups();
admingroups.setAdmingid(0);
userinfo.setAdmingroups(admingroups);
userManager.updateUserInfo(userinfo);
}
}
}
}
}
/**
* 保存论坛版块(分类)的相关信息
* @param forums
* @return
*/
public String saveForumsInf(Forums forums) {
forumManager.updateForum(forums);
setForumsPathList();
LForumCache.getInstance().removeCache("ForumListBoxOptions");
LForumCache.getInstance().removeCache("ForumList");
LForumCache.getInstance().removeCache("TopicTypesOption" + forums.getFid());
LForumCache.getInstance().removeCache("TopicTypesLink" + forums.getFid());
LForumCache.getInstance().removeCache("HotForumList");
LForumCache.getInstance().removeCache("ForumHotTopicList");
LForumCache.getInstance().removeCache("ForumNewTopicList");
if (logger.isDebugEnabled()) {
logger.debug("修改论坛板块 {} 成功", forums.getFid());
}
return setForumsModerators(forums.getFid(), forums.getForumfields().getModerators(), forums.getInheritedmod());
}
}
package com.javaeye.lonlysky.lforum.service;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Property;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springside.modules.orm.hibernate.SimpleHibernateTemplate;
import com.javaeye.lonlysky.lforum.cache.LForumCache;
import com.javaeye.lonlysky.lforum.comm.utils.Utils;
import com.javaeye.lonlysky.lforum.config.impl.ConfigLoader;
import com.javaeye.lonlysky.lforum.entity.forum.Bbcodes;
import com.javaeye.lonlysky.lforum.entity.forum.Forumlinks;
import com.javaeye.lonlysky.lforum.entity.forum.Forums;
import com.javaeye.lonlysky.lforum.entity.forum.Medals;
import com.javaeye.lonlysky.lforum.entity.forum.Onlinelist;
import com.javaeye.lonlysky.lforum.entity.forum.Smilies;
import com.javaeye.lonlysky.lforum.entity.forum.Templates;
import com.javaeye.lonlysky.lforum.entity.forum.Topicidentify;
/**
* 缓存论坛HTML数据
*
* @author 黄磊
*
*/
@Service
public class CachesManager {
private static final Object synObject = new Object();
private SimpleHibernateTemplate<Forumlinks, Integer> forumlinkDAO;
private SimpleHibernateTemplate<Topicidentify, Integer> topicidentifyDAO;
private SimpleHibernateTemplate<Medals, Integer> medalDAO;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
forumlinkDAO = new SimpleHibernateTemplate<Forumlinks, Integer>(sessionFactory, Forumlinks.class);
topicidentifyDAO = new SimpleHibernateTemplate<Topicidentify, Integer>(sessionFactory, Topicidentify.class);
medalDAO = new SimpleHibernateTemplate<Medals, Integer>(sessionFactory, Medals.class);
}
@Autowired
private TemplateManager templateManager;
@Autowired
private OnlineUserManager onlineUserManager;
@Autowired
private ForumManager forumManager;
@Autowired
private SmilieManager smilieManager;
@Autowired
private EditorManager editorManager;
/**
* 获得表情符的json数据
*
* @return 表情符的json数据
*/
public String getSmiliesCache() {
String smilesString = LForumCache.getInstance().getCache("SmiliesList", String.class);
if (smilesString == null) {
StringBuilder builder = new StringBuilder();
List<Smilies> smileList = smilieManager.getSmiliesList();
for (Smilies smilies : smileList) {
// 如果是表情分类
if (smilies.getType() == 0) {
builder.append("'" + smilies.getCode().trim().replace("'", "\\'") + "': [\r\n");
boolean flag = false;
for (Smilies smilies2 : smileList) {
// 如果属于以上表情分类
if (smilies2.getType().equals(smilies.getId())) {
builder.append("{'code' : '");
builder.append(smilies2.getCode().trim().replace("'", "\\'"));
builder.append("', 'url' : '");
builder.append(smilies2.getUrl().trim().replace("'", "\\'"));
builder.append("'},\r\n");
flag = true;
}
}//end for
if (builder.length() > 0 && flag) {
builder = builder.delete(builder.length() - 3, builder.length());
//builder = builder.deleteCharAt(builder.length() - 3);
}
builder.append("\r\n],\r\n");
}//end if
}//end for
builder = builder.delete(builder.length() - 3, builder.length());
smilesString = builder.toString();
LForumCache.getInstance().addCache("SmiliesList", smilesString);
}
return smilesString;
}
/**
* 获得表情分类列表
*
* @return 表情分类列表
*/
public List<Smilies> getSmilieTypesCache() {
List<Smilies> smilesList = LForumCache.getInstance().getListCache("SmilieTypes", Smilies.class);
if (smilesList == null) {
smilesList = smilieManager.getSmilieTypes();
LForumCache.getInstance().addCache("SmilieTypes", smilesList);
}
return smilesList;
}
/**
* 获取第一页的表情
*
* @return 获取第一页的表情
*/
public String getSmiliesFirstPageCache() {
String smiliesFirstPage = LForumCache.getInstance().getCache("SmiliesFirstPage", String.class);
if (smiliesFirstPage == null) {
StringBuilder builder = new StringBuilder();
List<Smilies> smileList = smilieManager.getSmiliesList();
for (int i = 0; i < smileList.size(); i++) {
// 如果是表情分类
if (smileList.get(i).getType() == 0) {
builder.append("'" + smileList.get(i).getCode().trim().replace("'", "\\'") + "': [\r\n");
boolean flag = false;
int smiliescount = 0;
for (int j = 0; j < smileList.size(); j++) {
// 如果属于以上表情分类
if (smileList.get(j).getType().equals(smileList.get(i).getId()) && smiliescount < 16) {
builder.append("{'code' : '");
builder.append(smileList.get(j).getCode().trim().replace("'", "\\'"));
builder.append("', 'url' : '");
builder.append(smileList.get(j).getUrl().trim().replace("'", "\\'"));
builder.append("'},\r\n");
flag = true;
smiliescount++;
}
}//end for
if (builder.length() > 0 && flag) {
builder = builder.delete(builder.length() - 3, builder.length());
}
builder.append("\r\n],\r\n");
}//end if
}//end for
builder = builder.delete(builder.length() - 3, builder.length());
smiliesFirstPage = builder.toString();
LForumCache.getInstance().addCache("SmiliesFirstPage", smiliesFirstPage);
}
return smiliesFirstPage;
}
/**
* 返回模板列表的下拉框html
*
* @return 下拉框html
*/
public String getTemplateListBoxOptionsCache() {
String templateListBoxOptions = LForumCache.getInstance().getCache("TemplateListBoxOptions", String.class);
if (templateListBoxOptions == null) {
synchronized (synObject) {
StringBuilder sb = new StringBuilder();
List<Templates> templateList = templateManager.getValidTemplateList();
for (Templates templates : templateList) {
sb.append("<li class=\"current\">");
sb.append("<a href=\"#\" onclick=\"window.location.href='showtemplate.action?templateid="
+ templates.getTemplateid() + "'\">");
sb.append(templates.getName().trim());
sb.append("</a>");
sb.append("</li>");
}
templateListBoxOptions = sb.toString();
LForumCache.getInstance().addCache("TemplateListBoxOptions", templateListBoxOptions);
}
}
return templateListBoxOptions;
}
/**
* 获得在线用户列表图例
* @return 在线用户列表图例
*/
public String getOnlineGroupIconList() {
String onlineGroupIconList = LForumCache.getInstance().getCache("OnlineGroupIconList", String.class);
if (onlineGroupIconList == null) {
List<Onlinelist> iconList = onlineUserManager.getOnlineGroupIconList();
StringBuilder sb = new StringBuilder();
for (Onlinelist onlinelist : iconList) {
sb.append("<img src=\"images/groupicons/");
sb.append(onlinelist.getImg());
sb.append("\" /> ");
sb.append(onlinelist.getTitle());
sb.append(" ");
}
onlineGroupIconList = sb.toString();
LForumCache.getInstance().addCache("OnlineGroupIconList", onlineGroupIconList);
}
return onlineGroupIconList;
}
/**
* 获得友情链接列表
* @return 友情链接列表
*/
@SuppressWarnings("unchecked")
public List<Forumlinks> getForumLinkList() {
List<Forumlinks> forumlinkList = LForumCache.getInstance().getListCache("ForumLinkList", Forumlinks.class);
if (forumlinkList == null) {
forumlinkList = forumlinkDAO.createCriteria(Property.forName("displayorder").gt(0)).add(
Property.forName("logo").ne("")).addOrder(Order.asc("displayorder")).list();
LForumCache.getInstance().addCache("ForumLinkList", forumlinkList);
}
return forumlinkList;
}
/**
* 前台版块列表弹出菜单
* @param usergroupid 用户组id
* @param userid 当前用户id
* @param extname 扩展名称
* @return 版块列表弹出菜单
*/
public String getForumListMenuDiv(int usergroupid, int userid, String extname) {
String forumListMenuDiv = LForumCache.getInstance().getCache("ForumListMenuDiv", String.class);
if (forumListMenuDiv == null) {
StringBuilder sb = new StringBuilder();
List<Forums> forumList = forumManager.getForumList();
if (forumList.size() > 0) {
sb
.append("<div class=\"popupmenu_popup\" id=\"forumlist_menu\" style=\"overflow-y: auto; display:none\">");
for (Forums forums : forumList) {
if (forums.getLayer() >= 0 && forums.getLayer() <= 1 && forums.getStatus() == 1) {
// 判断是否为私有论坛
if (!forums.getForumfields().getViewperm().trim().equals("")
&& !Utils.inArray(usergroupid + "", forums.getForumfields().getViewperm())) {
// 暂无处理
} else {
if (forums.getLayer() == 0) { // 如果是论坛分类
sb.append("<dl>");
sb.append("<dt>");
sb.append("<a href=\"showforum.action?forumid=");
sb.append(forums.getFid());
sb.append("\">");
sb.append(forums.getName());
sb.append("</a></dt>");
sb.append("<dd><ul>");
for (Forums forum : forumList) {
if (Utils.null2Int(forum.getParentidlist().split(",")[0].trim()) == forums.getFid()
&& forum.getLayer() == 1) {
// 判断是否为第一级板块
sb.append("<li><a href=\"showforum.action?forumid=");
sb.append(forum.getFid());
sb.append("\">");
sb.append(forum.getName());
sb.append("</a></li>");
}
}//end for
sb.append("</ul></dd>");
sb.append("</dl>");
}//end if
}//end if
}
}//end for
}
sb.append("</div>");
forumListMenuDiv = sb.toString().replace("<dd><ul></ul></dd>", "");
LForumCache.getInstance().addCache("ForumListMenuDiv", forumListMenuDiv);
}
return forumListMenuDiv;
}
/**
* 获得主题类型数组
*
* @return 主题类型数组
*/
@SuppressWarnings("unchecked")
public Map<Integer, Object> getTopicTypeArray() {
Map<Integer, Object> topicTypeArray = (Map<Integer, Object>) LForumCache.getInstance().getCache(
"TopicTypeArray");
if (topicTypeArray == null) {
topicTypeArray = new HashMap<Integer, Object>();
List<Object[]> objList = forumlinkDAO.find("select typeid,name from Topictypes order by displayorder");
if (objList.size() > 0) {
for (Object[] objects : objList) {
if (Utils.null2String(objects[0]) != "" && Utils.null2String(objects[1]) != "") {
topicTypeArray.put(Utils.null2Int(objects[0]), objects[1]);
}
}
}
LForumCache.getInstance().addCache("TopicTypeArray", topicTypeArray);
}
return topicTypeArray;
}
/**
* 获得版块下拉列表
* @return 列表内容的html
*/
@SuppressWarnings("unchecked")
public String getForumListBoxOptionsCache() {
String forumListBoxOptions = LForumCache.getInstance().getCache("ForumListBoxOptions", String.class);
if (forumListBoxOptions == null) {
StringBuilder sb = new StringBuilder();
List<Object[]> objList = forumlinkDAO
.find("select name,fid,layer from Forums where forums.fid not in (select fid from Forums where status<1 and layer=0) and status>0 and displayorder>=0 order by displayorder");
for (Object[] objects : objList) {
sb.append("<option value=\"");
sb.append(objects[1]);
sb.append("\">");
sb.append(Utils.getSpacesString(Utils.null2Int(objects[2], 0)));
sb.append(objects[0].toString().trim());
sb.append("</option>");
}
forumListBoxOptions = sb.toString();
LForumCache.getInstance().addCache("ForumListBoxOptions", forumListBoxOptions);
}
return forumListBoxOptions;
}
/**
* 获得编辑器自定义按钮信息的javascript数组
* @return 表情符的javascript数组
*/
public String getCustomEditButtonList() {
String str = LForumCache.getInstance().getCache("CustomEditButtonList", String.class);
if (str != null) {
return str;
}
StringBuilder sb = new StringBuilder();
List<Bbcodes> bbcodeList = editorManager.getCustomEditButtonList();
for (Bbcodes bbcodes : bbcodeList) {
//说明:[标签名,对应图标文件名,[参数1描述,参数2描述,...],[参数1默认值,参数2默认值,...]]
//实例["fly","swf.gif",["请输入flash网址","请输入flash宽度","请输入flash高度"],["http://","200","200"],3]
sb.append(",'" + Utils.replaceStrToScript(bbcodes.getTag()) + "':['");
sb.append(Utils.replaceStrToScript(bbcodes.getTag()));
sb.append("','");
sb.append(Utils.replaceStrToScript(bbcodes.getIcon()));
sb.append("','");
sb.append(Utils.replaceStrToScript(bbcodes.getExplanation()));
sb.append("',['");
sb.append(Utils.replaceStrToScript(bbcodes.getParamsdescript()).replace(",", "','"));
sb.append("'],['");
sb.append(Utils.replaceStrToScript(bbcodes.getParamsdefvalue()).replace(",", "','"));
sb.append("'],");
sb.append(Utils.replaceStrToScript(bbcodes.getParams().toString()));
sb.append("]");
}
if (sb.length() > 0) {
sb = sb.delete(0, 1);
}
str = sb.toString().replace("\r\n", "");
LForumCache.getInstance().addCache("CustomEditButtonList", str);
return str;
}
/**
* 返回脏字过滤列表
* @return 返回脏字过滤列表数组
*/
@SuppressWarnings("unchecked")
public String[][] getBanWordList() {
String[][] str = (String[][]) LForumCache.getInstance().getCache("BanWordList");
if (str != null) {
return str;
}
List list = forumlinkDAO.find("select find,replacement from Words");
if (list == null) {
return str;
}
List<Object[]> words = list;
str = new String[words.size()][2];
String temp = "";
Pattern pattern = Pattern.compile("\\{(\\d+)\\}");
for (int i = 0; i < words.size(); i++) {
temp = Utils.null2String(words.get(i)[0]);
Matcher matcher = pattern.matcher(temp);
for (int j = 0; j < matcher.groupCount(); j++) {
temp = temp.replace(matcher.group(j), matcher.group(j).replace("{", ".{0,"));
}
str[i][0] = temp.replace("\\", "\\\\").replace("\"", "\\\"").replace("'", "\'").replace("[", "\\[")
.replace("]", "\\]");
str[i][1] = Utils.null2String(words.get(i)[1]);
}
LForumCache.getInstance().addCache("BanWordList", str);
return str;
}
/**
* 替换原始字符串中的脏字词语
* @param text 原始字符串
* @return 替换后的结果
*/
public String banWordFilter(String text) {
StringBuilder sb = new StringBuilder(text);
String[][] str = getBanWordList();
int count = str.length / 2;
for (int i = 0; i < count; i++) {
if (!str[i][1].equals("{BANNED}") && !str[i][1].equals("{MOD}")) {
sb = new StringBuilder().append(sb.toString().replace(str[i][0], str[i][1]));
}
}
return sb.toString();
}
/**
* 判断字符串是否包含脏字词语
* @param text 原始字符串
* @return 如果包含则返回true, 否则返回false
*/
public boolean hasBannedWord(String text) {
String[][] str = getBanWordList();
int count = str.length / 2;
Pattern r_word = null;
for (int i = 0; i < count; i++) {
if (str[i][1].equals("{BANNED}")) {
r_word = Pattern.compile(str[i][0]);
Matcher matcher = r_word.matcher(text);
while (matcher.find()) {
return true;
}
}
}
return false;
}
/**
* 指定的字符串中是否含有需要审核词汇
*/
public boolean hasAuditWord(String text) {
String[][] str = getBanWordList();
int count = str.length / 2;
Pattern r_word = null;
for (int i = 0; i < count; i++) {
if (str[i][1].equals("{MOD}")) {
r_word = Pattern.compile(str[i][0]);
Matcher matcher = r_word.matcher(text);
while (matcher.find()) {
return true;
}
}
}
return false;
}
/**
* 获取主题鉴定项
* @param identifyid 主题签定id
* @return 主题鉴定信息
*/
public Topicidentify getTopicIdentify(int identifyid) {
for (Topicidentify ti : getTopicIdentifyList()) {
if (ti.getIdentifyid() == identifyid) {
return ti;
}
}
return new Topicidentify();
}
/**
* 获取主题签定集合项
* @return 主题签定集合项
*/
public List<Topicidentify> getTopicIdentifyList() {
List<Topicidentify> topicidentifyList = LForumCache.getInstance().getListCache("TopicIdentifyList",
Topicidentify.class);
if (topicidentifyList != null) {
return topicidentifyList;
}
topicidentifyList = new ArrayList<Topicidentify>();
topicidentifyList = topicidentifyDAO.findAll();
StringBuilder jsArray = new StringBuilder("<script type='text/javascript'>var topicidentify = { ");
for (Topicidentify topicidentify : topicidentifyList) {
jsArray.append("'" + topicidentify.getIdentifyid() + "':'" + topicidentify.getFilename() + "',");
}
jsArray.deleteCharAt(jsArray.length() - 1);
jsArray.append("};</script>");
LForumCache.getInstance().addCache("TopicIdentifyList", topicidentifyList);
LForumCache.getInstance().addCache("TopicIndentifysJsArray", jsArray.toString());
return topicidentifyList;
}
/**
* 获得勋章列表
* @return 勋章列表
*/
@SuppressWarnings("unchecked")
public Map<String, String> getMedalsList() {
Map<String, String> medalsMap = (Map<String, String>) LForumCache.getInstance().getCache("MedalsList");
if (medalsMap == null) {
medalsMap = new HashMap<String, String>();
List<Medals> medalList = medalDAO.findAll();
for (Medals medals : medalList) {
if (medals.getAvailable() == 1) {
if (!medals.getImage().trim().equals("")) {
medals.setImage("<img border=\"0\" src=\"images/medals/" + medals.getImage() + "\" alt=\""
+ medals.getName() + "\" title=\"" + medals.getName() + "\" class=\"medals\" />");
} else {
medals.setImage("");
}
} else {
medals.setImage("");
}
medalsMap.put(medals.getName(), medals.getImage());
}
LForumCache.getInstance().addCache("MedalsList", medalsMap);
}
return medalsMap;
}
/**
* 获取指定id的勋章列表html
* @param mdealList 勋章id
* @return 勋章列表html
*/
public String getMedalsList(String mdealList) {
Map<String, String> medalsMap = getMedalsList();
String[] list = mdealList.split(",");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.length; i++) {
sb.append(medalsMap.get(list[i]));
}
return sb.toString();
}
/**
* 获取自带头像列表
* @return 获取自带头像列表
*/
public List<String> getAvatarList() {
List<String> avatarList = LForumCache.getInstance().getListCache("CommonAvatarList", String.class);
if (avatarList == null) {
avatarList = new ArrayList<String>();
File dirinfo = new File(ConfigLoader.getConfig().getWebpath() + "avatars/common/");
String extname = "";
for (File file : dirinfo.listFiles()) {
if (file != null) {
extname = file.getName().substring(file.getName().lastIndexOf(".")).toLowerCase();
if (extname.equals(".jpg") || extname.equals(".gif") || extname.equals(".png")) {
avatarList.add("avatars/common/" + file.getName());
}
}
}
LForumCache.getInstance().addCache("CommonAvatarList", avatarList);
}
return avatarList;
}
/**
* 获取主题鉴定图片地址缓存数组
* @return 主题鉴定图片地址缓存数组
*/
public String getTopicIdentifyFileNameJsArray() {
String jsArray = LForumCache.getInstance().getCache("TopicIndentifysJsArray", String.class);
if (jsArray == null) {
getTopicIdentifyList();
jsArray = LForumCache.getInstance().getCache("TopicIndentifysJsArray", String.class);
}
return jsArray;
}
}
LForumCache
package com.javaeye.lonlysky.lforum.cache;
import java.net.URL;
import java.util.List;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* LForum缓存管理
*
* @author 黄磊
*
*/
public class LForumCache {
private static final Logger logger = LoggerFactory.getLogger(LForumCache.class);
private static Object lockObject = new Object();
private static LForumCache instance = null;
public static final String CACHE_NAME = "LForumCaches";
private LForumCache() {
}
/**
* 获取换此类实例
*
* @return LForumCache实例
*/
public static LForumCache getInstance() {
if (instance == null) {
synchronized (lockObject) {
logger.info("创建新的缓存类实例");
instance = new LForumCache();
}
}
return instance;
}
/**
* 获取缓存管理类
*
* @return 缓存管理类
*/
public CacheManager getCacheManager() {
URL url = getClass().getResource("/ehcache-hibernate.xml");
return CacheManager.create(url);
}
/**
* 获取论坛缓存
*
* @return 缓存
*/
public Cache getCache() {
return getCacheManager().getCache(CACHE_NAME);
}
/**
* 添加缓存对象
*
* @param key 缓存KEY
* @param cacheObj 缓存对象
*/
public void addCache(String key, Object cacheObj) {
getCache().put(new Element(key, cacheObj));
if (logger.isDebugEnabled()) {
logger.debug("添加KEY为{}的缓存对象,当前缓存{}个", key, getCache().getSize());
}
}
/**
* 获取缓存对象
*
* @param key 缓存KEY
* @return 缓存对象
*/
public Object getCache(String key) {
Element element = getCache().get(key);
if (element == null) {
return null;
}
if (logger.isDebugEnabled()) {
logger.debug("获取KEY为{}的缓存对象", key);
}
return (Object) element.getValue();
}
/**
* 获取指定类型缓存对象
*
* @param <T> 指定类型
* @param key 缓存KEY
* @param classz 类型Class
* @return 指定类型缓存对象
*/
@SuppressWarnings("unchecked")
public <T> T getCache(String key, Class<T> classz) {
if (getCache(key) == null) {
return null;
}
if (logger.isDebugEnabled()) {
logger.debug("获取KEY为{}且类型为{}的缓存对象", key, classz.getName());
}
return (T) getCache(key);
}
/**
* 获取指定类型LIST缓存
*
* @param <T> 指定类型
* @param key 缓存KEY
* @param classz 类型Class
* @return 指定类型LIST缓存
*/
@SuppressWarnings("unchecked")
public <T> List<T> getListCache(String key, Class<T> classz) {
if (getCache(key) == null) {
return null;
}
if (logger.isDebugEnabled()) {
logger.debug("获取KEY为{}且类型为{}的LIST缓存", key, classz.getName());
}
return (List<T>) getCache(key);
}
/**
* 移除指定KEY缓存对象
*
* @param key 缓存KEY
*/
public void removeCache(String key) {
if (getCache(key) != null) {
getCache().remove(key);
if (logger.isDebugEnabled()) {
logger.debug("移除取KEY为{}的缓存对象", key);
}
}
}
}
在各个manager操作中使用LForumCache缓存
package com.javaeye.lonlysky.lforum.service;
import java.util.List;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springside.modules.orm.hibernate.SimpleHibernateTemplate;
import com.javaeye.lonlysky.lforum.cache.LForumCache;
import com.javaeye.lonlysky.lforum.entity.forum.Announcements;
/**
* 论坛公告操作类
*
* @author 黄磊
*
*/
@Service
@Transactional
public class AnnouncementManager {
private final static Logger logger = LoggerFactory.getLogger(AnnouncementManager.class);
private SimpleHibernateTemplate<Announcements, Integer> announcementDAO;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
announcementDAO = new SimpleHibernateTemplate<Announcements, Integer>(sessionFactory, Announcements.class);
}
/**
* 获得全部指定时间段内的公告列表
* @param starttime 开始时间
* @param endtime 结束时间
* @return 公告列表
*/
@SuppressWarnings("unchecked")
public List<Announcements> getAnnouncementList(String starttime, String endtime) {
List<Announcements> announcemenList = LForumCache.getInstance().getListCache("AnnouncementList",
Announcements.class);
if (announcemenList == null) {
announcemenList = announcementDAO.find(
"from Announcements where starttime<=? and endtime>=? order by displayorder,id desc", starttime,
endtime);
if (logger.isDebugEnabled()) {
logger.debug("获得时间段" + starttime + " - " + endtime + "的公告列表");
}
LForumCache.getInstance().addCache("AnnouncementList", announcemenList);
}
return announcemenList;
}
/**
* 获得全部指定时间段内的前n条公告列表
* @param starttime 开始时间
* @param endtime 结束时间
* @param maxcount 最大记录数,小于0返回全部
* @return 公告列表
*/
@SuppressWarnings("unchecked")
public List<Announcements> getSimplifiedAnnouncementList(String starttime, String endtime, int maxcount) {
List<Announcements> announcemenList = LForumCache.getInstance().getListCache("SimplifiedAnnouncementList",
Announcements.class);
if (announcemenList == null) {
announcemenList = announcementDAO.createQuery(
"from Announcements where starttime<=? and endtime>=? order by displayorder,id desc", starttime,
endtime).setMaxResults(maxcount).list();
LForumCache.getInstance().addCache("SimplifiedAnnouncementList", announcemenList);
}
return announcemenList;
}
}
/**
* Copyright 2003-2009 Luck Consulting Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.ehcache.constructs.web.filter;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.constructs.blocking.BlockingCache;
import net.sf.ehcache.constructs.blocking.LockTimeoutException;
import net.sf.ehcache.constructs.web.AlreadyCommittedException;
import net.sf.ehcache.constructs.web.AlreadyGzippedException;
import net.sf.ehcache.constructs.web.GenericResponseWrapper;
import net.sf.ehcache.constructs.web.PageInfo;
import net.sf.ehcache.constructs.web.ResponseHeadersNotModifiableException;
import net.sf.ehcache.constructs.web.ResponseUtil;
import net.sf.ehcache.constructs.web.SerializableCookie;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Iterator;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.zip.DataFormatException;
/**
* An abstract CachingFilter.
* <p/>
* This class should be sub-classed for each page to be cached.
* <p/>
* The filters must be declared in the web.xml deployment descriptor. Then a mapping from a web resource,
* such as a JSP Page, FreeMarker page, Velocity page, Servlet or static resouce needs to be defined. Finally, a succession of mappings can be used
* to create a filter chain. See SRV.6 of the Servlet 2.3 specification for more details.
* <p/>
* Care should be taken not to define a filter chain such that the same {@link CachingFilter} class is reentered.
* The {@link CachingFilter} uses the {@link net.sf.ehcache.constructs.blocking.BlockingCache}. It blocks until the thread which
* did a get which results in a null does a put. If reentry happens a second get happens before the first put. The second
* get could wait indefinitely. This situation is monitored and if it happens, an IllegalStateException will be thrown.
* <p/>
* The following init-params are supported:
* <ol>
* <li>cacheName - the name in ehcache.xml used by the filter.
* <li>blockingTimeoutMillis - the time, in milliseconds, to wait for the filter chain to return with a response on a cache
* miss. This is useful to fail fast in the event of an infrastructure failure.
* </ol>
* @author @author Greg Luck
*/
public abstract class CachingFilter extends Filter {
private static final Logger LOG = Logger.getLogger(CachingFilter.class.getName());
private static final String BLOCKING_TIMEOUT_MILLIS = "blockingTimeoutMillis";
private static final String CACHE_NAME = "cacheName";
/**
* The cache name can be set through init parameters. If it is set it is stored here.
*/
protected String cacheName;
/**
* The cache holding the web pages. Ensure that all threads for a given cache name are using the same instance of this.
*/
protected BlockingCache blockingCache;
/**
* Initialises blockingCache to use. The BlockingCache created by this method does not have a lock timeout set.
* <p/>
* A timeout can be appled using <code>blockingCache.setTimeoutMillis(int timeout)</code> and takes effect immediately
* for all new requests
*
* @throws CacheException The most likely cause is that a cache has not been
* configured in ehcache's configuration file ehcache.xml for the filter name
* @param filterConfig this filter's configuration.
*/
public void doInit(FilterConfig filterConfig) throws CacheException {
synchronized (this.getClass()) {
if (blockingCache == null) {
setCacheNameIfAnyConfigured(filterConfig);
final String localCacheName = getCacheName();
Ehcache cache = getCacheManager().getEhcache(localCacheName);
if (!(cache instanceof BlockingCache)) {
//decorate and substitute
BlockingCache newBlockingCache = new BlockingCache(cache);
getCacheManager().replaceCacheWithDecoratedCache(cache, newBlockingCache);
}
blockingCache = (BlockingCache) getCacheManager().getEhcache(localCacheName);
Integer blockingTimeoutMillis = parseBlockingCacheTimeoutMillis(filterConfig);
if (blockingTimeoutMillis != null && blockingTimeoutMillis > 0) {
blockingCache.setTimeoutMillis(blockingTimeoutMillis);
}
}
}
}
/**
* Reads the filterConfig for the parameter "blockingTimeoutMillis", and if found,
* set the blocking timeout. If there is a parsing exception, no timeout is set.
*/
Integer parseBlockingCacheTimeoutMillis(FilterConfig filterConfig) {
String timeout = filterConfig.getInitParameter(BLOCKING_TIMEOUT_MILLIS);
try {
return Integer.parseInt(timeout);
} catch (NumberFormatException e) {
return null;
}
}
/**
* Sets the cacheName from the filterConfig
*/
protected void setCacheNameIfAnyConfigured(FilterConfig filterConfig) {
this.cacheName = filterConfig.getInitParameter(CACHE_NAME);
}
/**
* Destroys the filter.
*/
protected void doDestroy() {
//noop
}
/**
* Performs the filtering for a request. This method caches
* based responses keyed by {@link #calculateKey(javax.servlet.http.HttpServletRequest)}
* <p/>
* By default this method will queue requests requesting the page response for a given key
* until the first thread in the queue has completed. The request which occurs when the page
* expires incurs the cost of waiting for the downstream processing to return the respone.
* <p/>
* The maximum time to wait can be configured by setting <code>setTimeoutMillis</code> on the
* underlying <code>BlockingCache</code>.
*
* @param request
* @param response
* @param chain
* @throws AlreadyGzippedException if a double gzip is attempted
* @throws AlreadyCommittedException if the response was committed on the way in or the on the way back
* @throws FilterNonReentrantException if an attempt is made to reenter this filter in the same request.
* @throws LockTimeoutException if this request is waiting on another that is populating the cache entry
* and timeouts while waiting. Only occurs if the BlockingCache has a timeout set.
* @throws Exception for all other exceptions. They will be caught and logged in
* {@link Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}
*/
protected void doFilter(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain)
throws AlreadyGzippedException,
AlreadyCommittedException,
FilterNonReentrantException,
LockTimeoutException,
Exception {
if (response.isCommitted()) {
throw new AlreadyCommittedException("Response already committed before doing buildPage.");
}
logRequestHeaders(request);
PageInfo pageInfo = buildPageInfo(request, response, chain);
//return on error or redirect code
int statusCode = pageInfo.getStatusCode();
if (statusCode != HttpServletResponse.SC_OK) {
return;
}
if (response.isCommitted()) {
throw new AlreadyCommittedException("Response already committed after doing buildPage"
+ "but before writing response from PageInfo.");
}
writeResponse(request, response, pageInfo);
}
/**
* Build page info either using the cache or building the page directly.
* <p/>
* Some requests are for page fragments which should never be gzipped, or for
* other pages which are not gzipped.
*/
protected PageInfo buildPageInfo(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain) throws Exception {
// Look up the cached page
final String key = calculateKey(request);
PageInfo pageInfo = null;
String originalThreadName = Thread.currentThread().getName();
try {
checkNoReentry(request);
Element element = blockingCache.get(key);
if (element == null || element.getObjectValue() == null) {
try {
// Page is not cached - build the response, cache it, and send to client
pageInfo = buildPage(request, response, chain);
if (pageInfo.isOk()) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("PageInfo ok. Adding to cache " + blockingCache.getName() + " with key " + key);
}
blockingCache.put(new Element(key, pageInfo));
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("PageInfo was not ok(200). Putting null into cache " + blockingCache.getName()
+ " with key " + key);
}
blockingCache.put(new Element(key, null));
}
} catch (final Throwable throwable) {
// Must unlock the cache if the above fails. Will be logged at Filter
blockingCache.put(new Element(key, null));
throw new Exception(throwable);
}
} else {
pageInfo = (PageInfo) element.getObjectValue();
}
} catch (LockTimeoutException e) {
//do not release the lock, because you never acquired it
throw e;
} finally {
Thread.currentThread().setName(originalThreadName);
}
return pageInfo;
}
/**
* Builds the PageInfo object by passing the request along the filter chain
*
* @param request
* @param response
* @param chain
* @return a Serializable value object for the page or page fragment
* @throws AlreadyGzippedException if an attempt is made to double gzip the body
* @throws Exception
*/
protected PageInfo buildPage(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain) throws AlreadyGzippedException, Exception {
// Invoke the next entity in the chain
final ByteArrayOutputStream outstr = new ByteArrayOutputStream();
final GenericResponseWrapper wrapper = new GenericResponseWrapper(response, outstr);
chain.doFilter(request, wrapper);
wrapper.flush();
long timeToLiveSeconds = blockingCache.getCacheConfiguration().getTimeToLiveSeconds();
// Return the page info
return new PageInfo(wrapper.getStatus(), wrapper.getContentType(), wrapper.getHeaders(), wrapper.getCookies(),
outstr.toByteArray(), true, timeToLiveSeconds);
}
/**
* Writes the response from a PageInfo object.
* <p/>
* Headers are set last so that there is an opportunity to override
*
* @param request
* @param response
* @param pageInfo
* @throws IOException
* @throws DataFormatException
* @throws ResponseHeadersNotModifiableException
*
*/
protected void writeResponse(final HttpServletRequest request, final HttpServletResponse response, final PageInfo pageInfo)
throws IOException, DataFormatException, ResponseHeadersNotModifiableException {
boolean requestAcceptsGzipEncoding = acceptsGzipEncoding(request);
setStatus(response, pageInfo);
setContentType(response, pageInfo);
setCookies(pageInfo, response);
//do headers last so that users can override with their own header sets
setHeaders(pageInfo, requestAcceptsGzipEncoding, response);
writeContent(request, response, pageInfo);
}
/**
* Set the content type.
*
* @param response
* @param pageInfo
*/
protected void setContentType(final HttpServletResponse response, final PageInfo pageInfo) {
String contentType = pageInfo.getContentType();
if (contentType != null && contentType.length() > 0) {
response.setContentType(contentType);
}
}
/**
* Set the serializableCookies
*
* @param pageInfo
* @param response
*/
protected void setCookies(final PageInfo pageInfo, final HttpServletResponse response) {
final Collection cookies = pageInfo.getSerializableCookies();
for (Iterator iterator = cookies.iterator(); iterator.hasNext();) {
final Cookie cookie = ((SerializableCookie) iterator.next()).toCookie();
response.addCookie(cookie);
}
}
/**
* Status code
*
* @param response
* @param pageInfo
*/
protected void setStatus(final HttpServletResponse response, final PageInfo pageInfo) {
response.setStatus(pageInfo.getStatusCode());
}
/**
* Set the headers in the response object, excluding the Gzip header
*
* @param pageInfo
* @param requestAcceptsGzipEncoding
* @param response
*/
protected void setHeaders(final PageInfo pageInfo,
boolean requestAcceptsGzipEncoding,
final HttpServletResponse response) {
final Collection headers = pageInfo.getResponseHeaders();
final int header = 0;
final int value = 1;
for (Iterator iterator = headers.iterator(); iterator.hasNext();) {
final String[] headerPair = (String[]) iterator.next();
response.setHeader(headerPair[header], headerPair[value]);
}
}
/**
* A meaningful name representative of the JSP page being cached.
* <p/>
* The <code>cacheName</code> field is be set by the <code>doInit</code> method.
* Override to control the name used. The significance is that the name is used to find a cache
* configuration in <code>ehcache.xml</code>
*
* @return the name of the cache to use for this filter.
*/
protected String getCacheName() {
return cacheName;
}
/**
* Gets the CacheManager for this CachingFilter. It is therefore up to subclasses what CacheManager to use.
* <p/>
* This method was introduced in ehcache 1.2.1. Older versions used a singleton CacheManager instance created with
* the default factory method.
*
* @return the CacheManager to be used
* @since 1.2.1
*/
protected abstract CacheManager getCacheManager();
/**
* CachingFilter works off a key.
* <p/>
* The key should be unique. Factors to consider in generating a key are:
* <ul>
* <li>The various hostnames that a request could come through
* <li>Whether additional parameters used for referral tracking e.g. google should be excluded
* to maximise cache hits
* <li>Additional parameters can be added to any page. The page will still work but will miss the
* cache. Consider coding defensively around this issue.
* </ul>
* <p/>
* Implementers should differentiate between GET and HEAD requests otherwise blank pages
* can result. See SimplePageCachingFilter for an example implementation.
*
* @param httpRequest
* @return the key, generally the URL plus request parameters
*/
protected abstract String calculateKey(final HttpServletRequest httpRequest);
/**
* Writes the response content.
* This will be gzipped or non gzipped depending on whether the User Agent accepts
* GZIP encoding.
* <p/>
* If the body is written gzipped a gzip header is added.
*
* @param response
* @param pageInfo
* @throws IOException
*/
protected void writeContent(final HttpServletRequest request,
final HttpServletResponse response, final PageInfo pageInfo)
throws IOException, ResponseHeadersNotModifiableException {
byte[] body;
if (acceptsGzipEncoding(request)) {
ResponseUtil.addGzipHeader(response);
body = pageInfo.getGzippedBody();
if (ResponseUtil.shouldGzippedBodyBeZero(body, request)) {
body = new byte[0];
}
} else {
body = pageInfo.getUngzippedBody();
}
boolean shouldBodyBeZero = ResponseUtil.shouldBodyBeZero(request, pageInfo.getStatusCode());
if (shouldBodyBeZero) {
body = new byte[0];
}
response.setContentLength(body.length);
OutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(body);
out.flush();
}
/**
* Check that this caching filter is not being reentered by the same recursively.
* Recursive calls will block indefinitely because the first request has not yet
* unblocked the cache.
* <p/>
* This condition usually indicates an error in filter chaining or RequestDispatcher
* dispatching.
*
* @param httpRequest
* @throws FilterNonReentrantException if reentry is detected
*/
protected void checkNoReentry(final HttpServletRequest httpRequest) throws FilterNonReentrantException {
Thread thread = Thread.currentThread();
String threadName = thread.getName();
String filterName = getClass().getName();
if (thread.getName().indexOf(" been through " + filterName) != -1) {
throw new FilterNonReentrantException("The request thread is attempting to reenter"
+ " filter "
+ filterName
+ ". URL: "
+ httpRequest.getRequestURL());
}
//Instrument thread name
thread.setName(thread.getName() + " been through " + filterName);
String newThreadName = thread.getName();
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Thread name changed from " + threadName
+ " to " + newThreadName);
}
}
}
Ehcache-1.6.0源码\web\src\main\java\net\sf\ehcache\constructs\web\
/**
* Copyright 2003-2009 Luck Consulting Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.ehcache.constructs.web.filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A generic {@link javax.servlet.Filter} with most of what we need done.
* <p/>
* Participates in the Template Method pattern with {@link javax.servlet.Filter}.
*
* @author <a href="mailto:gluck@thoughtworks.com">Greg Luck</a>
* @version $Id: Filter.java 746 2008-08-18 08:08:02Z gregluck $
*/
public abstract class Filter implements javax.servlet.Filter {
/**
* If a request attribute NO_FILTER is set, then filtering will be skipped
*/
public static final String NO_FILTER = "NO_FILTER";
private static final Logger LOG = Logger.getLogger(Filter.class.getName());
/**
* The filter configuration.
*/
protected FilterConfig filterConfig;
/**
* The exceptions to log differently, as a comma separated list
*/
protected String exceptionsToLogDifferently;
/**
* A the level of the exceptions which will be logged differently.
* <p/>
* This should match the logging method name in Java logging e.g. fine
*/
protected Level exceptionsToLogDifferentlyLevel;
/**
* Most {@link Throwable}s in Web applications propagate to the user. Usually they are logged where they first
* happened. Printing the stack trace once a {@link Throwable} as propagated to the servlet is sometimes
* just clutters the log.
* <p/>
* This field corresponds to an init-param of the same name. If set to true stack traces will be suppressed.
*/
protected boolean suppressStackTraces;
/**
* Performs the filtering. This method calls template method
* {@link #doFilter(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,javax.servlet.FilterChain) } which does the filtering.
* This method takes care of error reporting and handling.
* Errors are reported at warn level because http tends to produce lots of errors.
*
* @throws IOException if an IOException occurs during this method it will be rethrown and will not be wrapped
*/
public final void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
throws ServletException, IOException {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
final HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
//NO_FILTER set for RequestDispatcher forwards to avoid double gzipping
if (filterNotDisabled(httpRequest)) {
doFilter(httpRequest, httpResponse, chain);
} else {
chain.doFilter(request, response);
}
} catch (final Throwable throwable) {
logThrowable(throwable, httpRequest);
}
}
/**
* Filters can be disabled programmatically by adding a {@link #NO_FILTER} parameter to the request.
* This parameter is normally added to make RequestDispatcher include and forwards work.
*
* @param httpRequest the request
* @return true if NO_FILTER is not set.
*/
protected boolean filterNotDisabled(final HttpServletRequest httpRequest) {
return httpRequest.getAttribute(NO_FILTER) == null;
}
/**
* This method should throw IOExceptions, not wrap them.
*/
private void logThrowable(final Throwable throwable, final HttpServletRequest httpRequest)
throws ServletException, IOException {
StringBuffer messageBuffer = new StringBuffer("Throwable thrown during doFilter on request with URI: ")
.append(httpRequest.getRequestURI())
.append(" and Query: ")
.append(httpRequest.getQueryString());
String message = messageBuffer.toString();
boolean matchFound = matches(throwable);
if (matchFound) {
try {
if (suppressStackTraces) {
LOG.log(exceptionsToLogDifferentlyLevel, throwable.getMessage());
} else {
LOG.log(exceptionsToLogDifferentlyLevel, throwable.getMessage(), throwable);
}
} catch (Exception e) {
LOG.log(Level.SEVERE, "Could not invoke Log method for " + exceptionsToLogDifferentlyLevel, e);
}
if (throwable instanceof IOException) {
throw (IOException) throwable;
} else {
throw new ServletException(message, throwable);
}
} else {
if (suppressStackTraces) {
LOG.log(Level.WARNING, messageBuffer.append(throwable.getMessage()).append("\nTop StackTraceElement: ")
.append(throwable.getStackTrace()[0].toString()).toString());
} else {
LOG.log(Level.WARNING, messageBuffer.append(throwable.getMessage()).toString(), throwable);
}
if (throwable instanceof IOException) {
throw (IOException) throwable;
} else {
throw new ServletException(throwable);
}
}
}
/**
* Checks whether a throwable, its root cause if it is a {@link ServletException}, or its cause, if it is a
* Chained Exception matches an entry in the exceptionsToLogDifferently list
*
* @param throwable
* @return true if the class name of any of the throwables is found in the exceptions to log differently
*/
private boolean matches(Throwable throwable) {
if (exceptionsToLogDifferently == null) {
return false;
}
if (exceptionsToLogDifferently.indexOf(throwable.getClass().getName()) != -1) {
return true;
}
if (throwable instanceof ServletException) {
Throwable rootCause = (((ServletException) throwable).getRootCause());
if (exceptionsToLogDifferently.indexOf(rootCause.getClass().getName()) != -1) {
return true;
}
}
if (throwable.getCause() != null) {
Throwable cause = throwable.getCause();
if (exceptionsToLogDifferently.indexOf(cause.getClass().getName()) != -1) {
return true;
}
}
return false;
}
/**
* Initialises the filter.
* <p/>
* Calls template method {@link #doInit(javax.servlet.FilterConfig)} to perform any filter specific initialisation.
*/
public final void init(final FilterConfig filterConfig) throws ServletException {
try {
this.filterConfig = filterConfig;
processInitParams(filterConfig);
// Attempt to initialise this filter
doInit(filterConfig);
} catch (final Exception e) {
LOG.log(Level.SEVERE, "Could not initialise servlet filter.", e);
throw new ServletException("Could not initialise servlet filter.", e);
}
}
/**
* Processes initialisation parameters. These are configured in web.xml in accordance with the
* Servlet specification using the following syntax:
* <pre>
* <filter>
* ...
* <init-param>
* <param-name>blah</param-name>
* <param-value>blahvalue</param-value>
* </init-param>
* ...
* </filter>
* </pre>
* @throws ServletException
*/
protected void processInitParams(final FilterConfig config) throws ServletException {
String exceptions = config.getInitParameter("exceptionsToLogDifferently");
String level = config.getInitParameter("exceptionsToLogDifferentlyLevel");
String suppressStackTracesString = config.getInitParameter("suppressStackTraces");
suppressStackTraces = Boolean.valueOf(suppressStackTracesString).booleanValue();
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Suppression of stack traces enabled for " + this.getClass().getName());
}
if (exceptions != null) {
validateMandatoryParameters(exceptions, level);
setLevel(level);
exceptionsToLogDifferently = exceptions;
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Different logging levels configured for " + this.getClass().getName());
}
}
}
private void validateMandatoryParameters(String exceptions, String level) throws ServletException {
if ((exceptions != null && level == null) || (level != null && exceptions == null)) {
throw new ServletException("Invalid init-params. Both exceptionsToLogDifferently"
+ " and exceptionsToLogDifferentlyLevelvalue should be specified if one is"
+ " specified.");
}
}
private void setLevel(String level) throws ServletException {
//Check correct level set
if (level.equals("finest")) {
exceptionsToLogDifferentlyLevel = Level.FINEST;
} else if (level.equals("fine")) {
exceptionsToLogDifferentlyLevel = Level.FINE;
} else if (level.equals("info")) {
exceptionsToLogDifferentlyLevel = Level.INFO;
} else if (level.equals("warning")) {
exceptionsToLogDifferentlyLevel = Level.WARNING;
} else if (level.equals("severe")) {
exceptionsToLogDifferentlyLevel = Level.SEVERE;
} else {
throw new ServletException("Invalid init-params value for \"exceptionsToLogDifferentlyLevel\"."
+ "Must be one of finest, fine, info, warning or severe.");
}
}
/**
* Destroys the filter. Calls template method {@link #doDestroy()} to perform any filter specific
* destruction tasks.
*/
public final void destroy() {
this.filterConfig = null;
doDestroy();
}
/**
* Checks if request accepts the named encoding.
*/
protected boolean acceptsEncoding(final HttpServletRequest request, final String name) {
final boolean accepts = headerContains(request, "Accept-Encoding", name);
return accepts;
}
/**
* Checks if request contains the header value.
*/
private boolean headerContains(final HttpServletRequest request, final String header, final String value) {
logRequestHeaders(request);
final Enumeration accepted = request.getHeaders(header);
while (accepted.hasMoreElements()) {
final String headerValue = (String) accepted.nextElement();
if (headerValue.indexOf(value) != -1) {
return true;
}
}
return false;
}
/**
* Logs the request headers, if debug is enabled.
*
* @param request
*/
protected void logRequestHeaders(final HttpServletRequest request) {
if (LOG.isLoggable(Level.FINE)) {
Map headers = new HashMap();
Enumeration enumeration = request.getHeaderNames();
StringBuffer logLine = new StringBuffer();
logLine.append("Request Headers");
while (enumeration.hasMoreElements()) {
String name = (String) enumeration.nextElement();
String headerValue = request.getHeader(name);
headers.put(name, headerValue);
logLine.append(": ").append(name).append(" -> ").append(headerValue);
}
LOG.fine(logLine.toString());
}
}
/**
* A template method that performs any Filter specific destruction tasks.
* Called from {@link #destroy()}
*/
protected abstract void doDestroy();
/**
* A template method that performs the filtering for a request.
* Called from {@link #doFilter(ServletRequest,ServletResponse,FilterChain)}.
*/
protected abstract void doFilter(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse,
final FilterChain chain) throws Throwable;
/**
* A template method that performs any Filter specific initialisation tasks.
* Called from {@link #init(FilterConfig)}.
* @param filterConfig
*/
protected abstract void doInit(FilterConfig filterConfig) throws Exception;
/**
* Returns the filter config.
*/
public FilterConfig getFilterConfig() {
return filterConfig;
}
/**
* Determine whether the user agent accepts GZIP encoding. This feature is part of HTTP1.1.
* If a browser accepts GZIP encoding it will advertise this by including in its HTTP header:
* <p/>
* <code>
* Accept-Encoding: gzip
* </code>
* <p/>
* Requests which do not accept GZIP encoding fall into the following categories:
* <ul>
* <li>Old browsers, notably IE 5 on Macintosh.
* <li>Search robots such as yahoo. While there are quite a few bots, they only hit individual
* pages once or twice a day. Note that Googlebot as of August 2004 now accepts GZIP.
* <li>Internet Explorer through a proxy. By default HTTP1.1 is enabled but disabled when going
* through a proxy. 90% of non gzip requests are caused by this.
* <li>Site monitoring tools
* </ul>
* As of September 2004, about 34% of requests coming from the Internet did not accept GZIP encoding.
*
* @param request
* @return true, if the User Agent request accepts GZIP encoding
*/
protected boolean acceptsGzipEncoding(HttpServletRequest request) {
return acceptsEncoding(request, "gzip");
}
}
/**
* Copyright 2003-2008 Luck Consulting Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.ehcache;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A Cache Element, consisting of a key, value and attributes.
* <p/>
* From ehcache-1.2, Elements can have keys and values that are Serializable or Objects. To preserve backward
* compatibility, special accessor methods for Object keys and values are provided: {@link #getObjectKey()} and
* {@link #getObjectValue()}. If placing Objects in ehcache, developers must use the new getObject... methods to
* avoid CacheExceptions. The get... methods are reserved for Serializable keys and values.
*
* @author Greg Luck
* @version $Id: Element.java 978 2009-06-16 23:29:59Z gregluck $
*/
public class Element implements Serializable, Cloneable {
/**
* serial version
* Updated for version 1.2 and again for 1.2.1
*/
private static final long serialVersionUID = 3343087714201120157L;
private static final Logger LOG = Logger.getLogger(Element.class.getName());
private static final long ONE_SECOND = 1000L;
/**
* the cache key.
*/
private final Object key;
/**
* the value.
*/
private Object value;
/**
* version of the element. System.currentTimeMillis() is used to compute version for updated elements. That
* way, the actual version of the updated element does not need to be checked.
*/
private long version;
/**
* The creation time.
*/
private long creationTime;
/**
* The last access time.
*/
private long lastAccessTime;
/**
* The next to last access time. Used by the expiry mechanism
*/
private long nextToLastAccessTime;
/**
* The number of times the element was hit.
*/
private long hitCount;
/**
* The amount of time for the element to live, in seconds. 0 indicates unlimited.
*/
private int timeToLive;
/**
* The amount of time for the element to idle, in seconds. 0 indicates unlimited.
*/
private int timeToIdle;
/**
* If there is an Element in the Cache and it is replaced with a new Element for the same key,
* then both the version number and lastUpdateTime should be updated to reflect that. The creation time
* will be the creation time of the new Element, not the original one, so that TTL concepts still work.
*/
private long lastUpdateTime;
/**
* Whether the element is eternal, i.e. never expires.
*/
private boolean eternal;
/**
* Whether any combination of eternal, TTL or TTI has been set.
*/
private boolean lifespanSet;
/**
* A full constructor.
* <p/>
* Creation time is set to the current time. Last Access Time and Previous To Last Access Time
* are not set.
*
* @since .4
*/
public Element(Serializable key, Serializable value, long version) {
this((Object) key, (Object) value, version);
}
/**
* A full constructor.
* <p/>
* Creation time is set to the current time. Last Access Time and Previous To Last Access Time
* are not set.
*
* @since 1.2
*/
public Element(Object key, Object value, long version) {
this.key = key;
this.value = value;
this.version = version;
creationTime = System.currentTimeMillis();
hitCount = 0;
}
/**
* Constructor.
*
* @since 1.3
*/
public Element(Object key, Object value, long version,
long creationTime, long lastAccessTime,
long nextToLastAccessTime, long lastUpdateTime,
long hitCount) {
this.key = key;
this.value = value;
this.version = version;
this.creationTime = creationTime;
this.lastAccessTime = lastAccessTime;
this.nextToLastAccessTime = nextToLastAccessTime;
this.lastUpdateTime = lastUpdateTime;
this.hitCount = hitCount;
}
/**
* Constructor used by ehcache-server
*
* @param key any non null value
* @param value any value, including nulls
* @param eternal specify as non-null to override cache configuration
* @param timeToIdleSeconds specify as non-null to override cache configuration
* @param timeToLiveSeconds specify as non-null to override cache configuration
*/
public Element(Object key, Object value,
Boolean eternal, Integer timeToIdleSeconds, Integer timeToLiveSeconds) {
this.key = key;
this.value = value;
if (eternal != null) {
setEternal(eternal.booleanValue());
}
if (timeToIdleSeconds != null) {
setTimeToIdle(timeToIdleSeconds.intValue());
}
if (timeToLiveSeconds != null) {
setTimeToLive(timeToLiveSeconds.intValue());
}
creationTime = System.currentTimeMillis();
}
/**
* Constructor.
*
* @param key
* @param value
*/
public Element(Serializable key, Serializable value) {
this((Object) key, (Object) value, 1L);
}
/**
* Constructor.
*
* @param key
* @param value
* @since 1.2
*/
public Element(Object key, Object value) {
this(key, value, 1L);
}
/**
* Gets the key attribute of the Element object.
*
* @return The key value. If the key is not Serializable, null is returned and an info log message emitted
* @see #getObjectKey()
*/
public final Serializable getKey() {
Serializable keyAsSerializable;
try {
keyAsSerializable = (Serializable) key;
} catch (Exception e) {
throw new CacheException("The key " + key + " is not Serializable. Consider using Element#getObjectKey()");
}
return keyAsSerializable;
}
/**
* Gets the key attribute of the Element object.
* <p/>
* This method is provided for those wishing to use ehcache as a memory only cache
* and enables retrieval of non-Serializable values from elements.
*
* @return The key as an Object. i.e no restriction is placed on it
* @see #getKey()
*/
public final Object getObjectKey() {
return key;
}
/**
* Gets the value attribute of the Element object.
*
* @return The value which must be Serializable. If not use {@link #getObjectValue}. If the value is not Serializable, null is returned and an info log message emitted
* @see #getObjectValue()
*/
public final Serializable getValue() {
Serializable valueAsSerializable;
try {
valueAsSerializable = (Serializable) value;
} catch (Exception e) {
throw new CacheException("The value " + value + " for key " + key +
" is not Serializable. Consider using Element#getObjectKey()");
}
return valueAsSerializable;
}
/**
* Gets the value attribute of the Element object as an Object.
* <p/>
* This method is provided for those wishing to use ehcache as a memory only cache
* and enables retrieval of non-Serializable values from elements.
*
* @return The value as an Object. i.e no restriction is placed on it
* @see #getValue()
* @since 1.2
*/
public final Object getObjectValue() {
return value;
}
/**
* Equals comparison with another element, based on the key.
*/
public final boolean equals(Object object) {
if (object == null || !(object instanceof Element)) {
return false;
}
Element element = (Element) object;
if (key == null || element.getObjectKey() == null) {
return false;
}
return key.equals(element.getObjectKey());
}
/**
* Sets time to Live
*
* @param timeToLiveSeconds the number of seconds to live
*/
public void setTimeToLive(int timeToLiveSeconds) {
this.timeToLive = timeToLiveSeconds;
lifespanSet = true;
}
/**
* Sets time to idle
*
* @param timeToIdleSeconds the number of seconds to idle
*/
public void setTimeToIdle(int timeToIdleSeconds) {
this.timeToIdle = timeToIdleSeconds;
lifespanSet = true;
}
/**
* Gets the hashcode, based on the key.
*/
public final int hashCode() {
return key.hashCode();
}
/**
* Sets the version attribute of the ElementAttributes object.
*
* @param version The new version value
*/
public final void setVersion(long version) {
this.version = version;
}
/**
* Gets the creationTime attribute of the ElementAttributes object.
*
* @return The creationTime value
*/
public final long getCreationTime() {
return creationTime;
}
/**
* Calculates the latest of creation and update time
* @return if never updated, creation time is returned, otherwise updated time
*/
public final long getLatestOfCreationAndUpdateTime() {
if (lastUpdateTime == 0) {
return creationTime;
} else {
return lastUpdateTime;
}
}
/**
* Sets the creationTime attribute of the ElementAttributes object.
*/
public final void setCreateTime() {
creationTime = System.currentTimeMillis();
}
/**
* Gets the version attribute of the ElementAttributes object.
*
* @return The version value
*/
public final long getVersion() {
return version;
}
/**
* Gets the last access time.
* Access means a get. So a newly created {@link Element}
* will have a last access time equal to its create time.
*/
public final long getLastAccessTime() {
return lastAccessTime;
}
/**
* Gets the next to last access time.
*
* @see #getLastAccessTime()
*/
public final long getNextToLastAccessTime() {
return nextToLastAccessTime;
}
/**
* Gets the hit count on this element.
*/
public final long getHitCount() {
return hitCount;
}
/**
* Resets the hit count to 0 and the last access time to 0.
*/
public final void resetAccessStatistics() {
lastAccessTime = 0;
nextToLastAccessTime = 0;
hitCount = 0;
}
/**
* Sets the last access time to now.
*/
public final void updateAccessStatistics() {
nextToLastAccessTime = lastAccessTime;
lastAccessTime = System.currentTimeMillis();
hitCount++;
}
/**
* Sets the last access time to now.
*/
public final void updateUpdateStatistics() {
lastUpdateTime = System.currentTimeMillis();
version = lastUpdateTime;
}
/**
* Returns a {@link String} representation of the {@link Element}.
*/
public final String toString() {
StringBuffer sb = new StringBuffer();
sb.append("[ key = ").append(key)
.append(", value=").append(value)
.append(", version=").append(version)
.append(", hitCount=").append(hitCount)
.append(", CreationTime = ").append(this.getCreationTime())
.append(", LastAccessTime = ").append(this.getLastAccessTime())
.append(" ]");
return sb.toString();
}
/**
* Clones an Element. A completely new object is created, with no common references with the
* existing one.
* <p/>
* This method will not work unless the Object is Serializable
* <p/>
* Warning: This can be very slow on large object graphs. If you use this method
* you should write a performance test to verify suitability.
*
* @return a new {@link Element}, with exactly the same field values as the one it was cloned from.
* @throws CloneNotSupportedException
*/
public final Object clone() throws CloneNotSupportedException {
//Not used. Just to get code inspectors to shut up
super.clone();
Element element = new Element(deepCopy(key), deepCopy(value), version);
element.creationTime = creationTime;
element.lastAccessTime = lastAccessTime;
element.nextToLastAccessTime = nextToLastAccessTime;
element.hitCount = hitCount;
return element;
}
private Object deepCopy(Object oldValue) {
Serializable newValue = null;
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
oos = new ObjectOutputStream(bout);
oos.writeObject(oldValue);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ois = new ObjectInputStream(bin);
newValue = (Serializable) ois.readObject();
} catch (IOException e) {
LOG.log(Level.SEVERE, "Error cloning Element with key " + key
+ " during serialization and deserialization of value");
} catch (ClassNotFoundException e) {
LOG.log(Level.SEVERE, "Error cloning Element with key " + key
+ " during serialization and deserialization of value");
} finally {
try {
if (oos != null) {
oos.close();
}
if (ois != null) {
ois.close();
}
} catch (Exception e) {
LOG.log(Level.SEVERE, "Error closing Stream");
}
}
return newValue;
}
/**
* The size of this object in serialized form. This is not the same
* thing as the memory size, which is JVM dependent. Relative values should be meaningful,
* however.
* <p/>
* Warning: This method can be <b>very slow</b> for values which contain large object graphs.
* <p/>
* If the key or value of the Element is not Serializable, an error will be logged and 0 will be returned.
*
* @return The serialized size in bytes
*/
public final long getSerializedSize() {
if (!isSerializable()) {
return 0;
}
long size = 0;
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(bout);
oos.writeObject(this);
size = bout.size();
return size;
} catch (IOException e) {
LOG.log(Level.FINE, "Error measuring element size for element with key " + key + ". Cause was: " + e.getMessage());
} finally {
try {
if (oos != null) {
oos.close();
}
} catch (Exception e) {
LOG.log(Level.SEVERE, "Error closing ObjectOutputStream");
}
}
return size;
}
/**
* Whether the element may be Serialized.
* <p/>
* While Element implements Serializable, it is possible to create non Serializable elements
* for use in MemoryStores. This method checks that an instance of Element really is Serializable
* and will not throw a NonSerializableException if Serialized.
* <p/>
* This method was tweaked in 1.6 as it has been shown that Serializable classes can be serializaed as can
* null, regardless of what class it is a null of. ObjectOutputStream.write(null) works and ObjectInputStream.read()
* will read null back.
*
* @return true if the element is Serializable
* @since 1.2
*/
public final boolean isSerializable() {
return isKeySerializable() && (value instanceof Serializable || value == null);
}
/**
* Whether the element's key may be Serialized.
* <p/>
* While Element implements Serializable, it is possible to create non Serializable elements and/or
* non Serializable keys for use in MemoryStores.
* <p/>
* This method checks that an instance of an Element's key really is Serializable
* and will not throw a NonSerializableException if Serialized.
*
* @return true if the element's key is Serializable
* @since 1.2
*/
public final boolean isKeySerializable() {
return key instanceof Serializable || key == null;
}
/**
* If there is an Element in the Cache and it is replaced with a new Element for the same key,
* then both the version number and lastUpdateTime should be updated to reflect that. The creation time
* will be the creation time of the new Element, not the original one, so that TTL concepts still work.
*
* @return the time when the last update occured. If this is the original Element, the time will be null
*/
public long getLastUpdateTime() {
return lastUpdateTime;
}
/**
* An element is expired if the expiration time as given by {@link #getExpirationTime()} is in the past.
*
* @return true if the Element is expired, otherwise false. If no lifespan has been set for the Element it is
* considered not able to expire.
* @see #getExpirationTime()
*/
public boolean isExpired() {
if (!lifespanSet) {
return false;
}
long now = System.currentTimeMillis();
long expirationTime = getExpirationTime();
return now > expirationTime;
}
/**
* Returns the expiration time based on time to live. If this element also has a time to idle setting, the expiry
* time will vary depending on whether the element is accessed.
*
* @return the time to expiration
*/
public long getExpirationTime() {
if (!lifespanSet || eternal || (timeToLive == 0 && timeToIdle == 0)) {
return Long.MAX_VALUE;
}
long expirationTime = 0;
long ttlExpiry = creationTime + timeToLive * ONE_SECOND;
long mostRecentTime = Math.max(creationTime, nextToLastAccessTime);
long ttiExpiry = mostRecentTime + timeToIdle * ONE_SECOND;
if (timeToLive != 0 && (timeToIdle == 0 || lastAccessTime == 0)) {
expirationTime = ttlExpiry;
} else if (timeToLive == 0) {
expirationTime = ttiExpiry;
} else {
expirationTime = Math.min(ttlExpiry, ttiExpiry);
}
return expirationTime;
}
/**
* @return true if the element is eternal
*/
public boolean isEternal() {
return eternal;
}
/**
* Sets whether the element is eternal.
*
* @param eternal
*/
public void setEternal(boolean eternal) {
this.eternal = eternal;
lifespanSet = true;
}
/**
* Whether any combination of eternal, TTL or TTI has been set.
*
* @return true if set.
*/
public boolean isLifespanSet() {
return lifespanSet;
}
/**
* @return the time to live, in seconds
*/
public int getTimeToLive() {
return timeToLive;
}
/**
* @return the time to idle, in seconds
*/
public int getTimeToIdle() {
return timeToIdle;
}
}
/**
* Copyright 2003-2008 Luck Consulting Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.ehcache;
import net.sf.ehcache.bootstrap.BootstrapCacheLoader;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.event.RegisteredEventListeners;
import net.sf.ehcache.exceptionhandler.CacheExceptionHandler;
import net.sf.ehcache.extension.CacheExtension;
import net.sf.ehcache.loader.CacheLoader;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* An interface for Ehcache.
* <p/>
* Ehcache is the central interface. Caches have {@link Element}s and are managed
* by the {@link CacheManager}. The Cache performs logical actions. It delegates physical
* implementations to its {@link net.sf.ehcache.store.Store}s.
* <p/>
* A reference to an EhCache can be obtained through the {@link CacheManager}. An Ehcache thus obtained
* is guaranteed to have status {@link Status#STATUS_ALIVE}. This status is checked for any method which
* throws {@link IllegalStateException} and the same thrown if it is not alive. This would normally
* happen if a call is made after {@link CacheManager#shutdown} is invoked.
* <p/>
* Statistics on cache usage are collected and made available through public methods.
*
* @author Greg Luck
* @version $Id: Ehcache.java 885 2009-01-29 07:38:23Z gregluck $
*/
public interface Ehcache extends Cloneable {
/**
* Put an element in the cache.
* <p/>
* Resets the access statistics on the element, which would be the case if it has previously been
* gotten from a cache, and is now being put back.
* <p/>
* Also notifies the CacheEventListener that:
* <ul>
* <li>the element was put, but only if the Element was actually put.
* <li>if the element exists in the cache, that an update has occurred, even if the element would be expired
* if it was requested
* </ul>
*
* @param element An object. If Serializable it can fully participate in replication and the DiskStore.
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
* @throws IllegalArgumentException if the element is null
* @throws CacheException
*/
void put(Element element) throws IllegalArgumentException, IllegalStateException,
CacheException;
/**
* Put an element in the cache.
* <p/>
* Resets the access statistics on the element, which would be the case if it has previously been
* gotten from a cache, and is now being put back.
* <p/>
* Also notifies the CacheEventListener that:
* <ul>
* <li>the element was put, but only if the Element was actually put.
* <li>if the element exists in the cache, that an update has occurred, even if the element would be expired
* if it was requested
* </ul>
*
* @param element An object. If Serializable it can fully participate in replication and the DiskStore.
* @param doNotNotifyCacheReplicators whether the put is coming from a doNotNotifyCacheReplicators cache peer, in which case this put should not initiate a
* further notification to doNotNotifyCacheReplicators cache peers
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
* @throws IllegalArgumentException if the element is null
*/
void put(Element element, boolean doNotNotifyCacheReplicators) throws IllegalArgumentException,
IllegalStateException,
CacheException;
/**
* Put an element in the cache, without updating statistics, or updating listeners. This is meant to be used
* in conjunction with {@link #getQuiet}
*
* @param element An object. If Serializable it can fully participate in replication and the DiskStore.
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
* @throws IllegalArgumentException if the element is null
*/
void putQuiet(Element element) throws IllegalArgumentException, IllegalStateException,
CacheException;
/**
* Gets an element from the cache. Updates Element Statistics
* <p/>
* Note that the Element's lastAccessTime is always the time of this get.
* Use {@link #getQuiet(Object)} to peak into the Element to see its last access time with get
*
* @param key a serializable value
* @return the element, or null, if it does not exist.
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
* @see #isExpired
*/
Element get(Serializable key) throws IllegalStateException, CacheException;
/**
* Gets an element from the cache. Updates Element Statistics
* <p/>
* Note that the Element's lastAccessTime is always the time of this get.
* Use {@link #getQuiet(Object)} to peak into the Element to see its last access time with get
*
* @param key an Object value
* @return the element, or null, if it does not exist.
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
* @see #isExpired
* @since 1.2
*/
Element get(Object key) throws IllegalStateException, CacheException;
/**
* Gets an element from the cache, without updating Element statistics. Cache statistics are
* still updated.
* <p/>
*
* @param key a serializable value
* @return the element, or null, if it does not exist.
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
* @see #isExpired
*/
Element getQuiet(Serializable key) throws IllegalStateException, CacheException;
/**
* Gets an element from the cache, without updating Element statistics. Cache statistics are
* also not updated.
* <p/>
*
* @param key a serializable value
* @return the element, or null, if it does not exist.
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
* @see #isExpired
* @since 1.2
*/
Element getQuiet(Object key) throws IllegalStateException, CacheException;
/**
* Returns a list of all elements in the cache, whether or not they are expired.
* <p/>
* The returned keys are unique and can be considered a set.
* <p/>
* The List returned is not live. It is a copy.
* <p/>
* The time taken is O(n). On a single cpu 1.8Ghz P4, approximately 8ms is required
* for each 1000 entries.
*
* @return a list of {@link Object} keys
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
*/
List getKeys() throws IllegalStateException, CacheException;
/**
* Returns a list of all elements in the cache. Only keys of non-expired
* elements are returned.
* <p/>
* The returned keys are unique and can be considered a set.
* <p/>
* The List returned is not live. It is a copy.
* <p/>
* The time taken is O(n), where n is the number of elements in the cache. On
* a 1.8Ghz P4, the time taken is approximately 200ms per 1000 entries. This method
* is not synchronized, because it relies on a non-live list returned from {@link #getKeys()}
* , which is synchronised, and which takes 8ms per 1000 entries. This way
* cache liveness is preserved, even if this method is very slow to return.
* <p/>
* Consider whether your usage requires checking for expired keys. Because
* this method takes so long, depending on cache settings, the list could be
* quite out of date by the time you get it.
*
* @return a list of {@link Object} keys
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
*/
List getKeysWithExpiryCheck() throws IllegalStateException, CacheException;
/**
* Returns a list of all elements in the cache, whether or not they are expired.
* <p/>
* The returned keys are not unique and may contain duplicates. If the cache is only
* using the memory store, the list will be unique. If the disk store is being used
* as well, it will likely contain duplicates, because of the internal store design.
* <p/>
* The List returned is not live. It is a copy.
* <p/>
* The time taken is O(log n). On a single cpu 1.8Ghz P4, approximately 6ms is required
* for 1000 entries and 36 for 50000.
* <p/>
* This is the fastest getKeys method
*
* @return a list of {@link Object} keys
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
*/
List getKeysNoDuplicateCheck() throws IllegalStateException;
/**
* Removes an {@link net.sf.ehcache.Element} from the Cache. This also removes it from any
* stores it may be in.
* <p/>
* Also notifies the CacheEventListener after the element was removed.
*
* @param key
* @return true if the element was removed, false if it was not found in the cache
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
*/
boolean remove(Serializable key) throws IllegalStateException;
/**
* Removes an {@link net.sf.ehcache.Element} from the Cache. This also removes it from any
* stores it may be in.
* <p/>
* Also notifies the CacheEventListener after the element was removed, but only if an Element
* with the key actually existed.
*
* @param key
* @return true if the element was removed, false if it was not found in the cache
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
* @since 1.2
*/
boolean remove(Object key) throws IllegalStateException;
/**
* Removes an {@link net.sf.ehcache.Element} from the Cache. This also removes it from any
* stores it may be in.
* <p/>
* Also notifies the CacheEventListener after the element was removed, but only if an Element
* with the key actually existed.
*
* @param key
* @param doNotNotifyCacheReplicators whether the put is coming from a doNotNotifyCacheReplicators cache peer, in which case this put should not initiate a
* further notification to doNotNotifyCacheReplicators cache peers
* @return true if the element was removed, false if it was not found in the cache
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
*/
boolean remove(Serializable key, boolean doNotNotifyCacheReplicators) throws IllegalStateException;
/**
* Removes an {@link net.sf.ehcache.Element} from the Cache. This also removes it from any
* stores it may be in.
* <p/>
* Also notifies the CacheEventListener after the element was removed, but only if an Element
* with the key actually existed.
*
* @param key
* @param doNotNotifyCacheReplicators whether the put is coming from a doNotNotifyCacheReplicators cache peer, in which case this put should not initiate a
* further notification to doNotNotifyCacheReplicators cache peers
* @return true if the element was removed, false if it was not found in the cache
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
*/
boolean remove(Object key, boolean doNotNotifyCacheReplicators) throws IllegalStateException;
/**
* Removes an {@link net.sf.ehcache.Element} from the Cache, without notifying listeners. This also removes it from any
* stores it may be in.
* <p/>
*
* @param key
* @return true if the element was removed, false if it was not found in the cache
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
*/
boolean removeQuiet(Serializable key) throws IllegalStateException;
/**
* Removes an {@link net.sf.ehcache.Element} from the Cache, without notifying listeners. This also removes it from any
* stores it may be in.
* <p/>
*
* @param key
* @return true if the element was removed, false if it was not found in the cache
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
* @since 1.2
*/
boolean removeQuiet(Object key) throws IllegalStateException;
/**
* Removes all cached items.
*
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
*/
void removeAll() throws IllegalStateException, CacheException;
/**
* Removes all cached items.
*
* @param doNotNotifyCacheReplicators whether the put is coming from a doNotNotifyCacheReplicators cache peer,
* in which case this put should not initiate a further notification to doNotNotifyCacheReplicators cache peers
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
*/
void removeAll(boolean doNotNotifyCacheReplicators) throws IllegalStateException, CacheException;
/**
* Flushes all cache items from memory to the disk store, and from the DiskStore to disk.
*
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
*/
void flush() throws IllegalStateException, CacheException;
/**
* Gets the size of the cache. This is a subtle concept. See below.
* <p/>
* The size is the number of {@link net.sf.ehcache.Element}s in the {@link net.sf.ehcache.store.MemoryStore} plus
* the number of {@link net.sf.ehcache.Element}s in the {@link net.sf.ehcache.store.DiskStore}.
* <p/>
* This number is the actual number of elements, including expired elements that have
* not been removed.
* <p/>
* Expired elements are removed from the the memory store when
* getting an expired element, or when attempting to spool an expired element to
* disk.
* <p/>
* Expired elements are removed from the disk store when getting an expired element,
* or when the expiry thread runs, which is once every five minutes.
* <p/>
* To get an exact size, which would exclude expired elements, use {@link #getKeysWithExpiryCheck()}.size(),
* although see that method for the approximate time that would take.
* <p/>
* To get a very fast result, use {@link #getKeysNoDuplicateCheck()}.size(). If the disk store
* is being used, there will be some duplicates.
*
* @return The size value
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
*/
int getSize() throws IllegalStateException, CacheException;
/**
* Gets the size of the memory store for this cache
* <p/>
* Warning: This method can be very expensive to run. Allow approximately 1 second
* per 1MB of entries. Running this method could create liveness problems
* because the object lock is held for a long period
* <p/>
*
* @return the approximate size of the memory store in bytes
* @throws IllegalStateException
*/
long calculateInMemorySize() throws IllegalStateException, CacheException;
/**
* Returns the number of elements in the memory store.
*
* @return the number of elements in the memory store
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
*/
long getMemoryStoreSize() throws IllegalStateException;
/**
* Returns the number of elements in the disk store.
*
* @return the number of elements in the disk store.
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
*/
int getDiskStoreSize() throws IllegalStateException;
/**
* Gets the status attribute of the Cache.
*
* @return The status value from the Status enum class
*/
Status getStatus();
/**
* Gets the cache name.
*/
String getName();
/**
* Sets the cache name which will name.
*
* @param name the name of the cache. Should not be null.
*/
void setName(String name);
/**
* Returns a {@link String} representation of {@link net.sf.ehcache.Cache}.
*/
String toString();
/**
* Checks whether this cache element has expired.
* <p/>
* The element is expired if:
* <ol>
* <li> the idle time is non-zero and has elapsed, unless the cache is eternal; or
* <li> the time to live is non-zero and has elapsed, unless the cache is eternal; or
* <li> the value of the element is null.
* </ol>
*
* @param element the element to check
* @return true if it has expired
* @throws IllegalStateException if the cache is not {@link net.sf.ehcache.Status#STATUS_ALIVE}
* @throws NullPointerException if the element is null
*/
boolean isExpired(Element element) throws IllegalStateException, NullPointerException;
/**
* Clones a cache. This is only legal if the cache has not been
* initialized. At that point only primitives have been set and no
* {@link net.sf.ehcache.store.MemoryStore} or {@link net.sf.ehcache.store.DiskStore} has been created.
* <p/>
* A new, empty, RegisteredEventListeners is created on clone.
* <p/>
*
* @return an object of type {@link net.sf.ehcache.Cache}
* @throws CloneNotSupportedException
*/
Object clone() throws CloneNotSupportedException;
/**
* Use this to access the service in order to register and unregister listeners
*
* @return the RegisteredEventListeners instance for this cache.
*/
RegisteredEventListeners getCacheEventNotificationService();
/**
* Whether an Element is stored in the cache in Memory, indicating a very low cost of retrieval.
*
* @return true if an element matching the key is found in memory
*/
boolean isElementInMemory(Serializable key);
/**
* Whether an Element is stored in the cache in Memory, indicating a very low cost of retrieval.
*
* @return true if an element matching the key is found in memory
* @since 1.2
*/
boolean isElementInMemory(Object key);
/**
* Whether an Element is stored in the cache on Disk, indicating a higher cost of retrieval.
*
* @return true if an element matching the key is found in the diskStore
*/
boolean isElementOnDisk(Serializable key);
/**
* Whether an Element is stored in the cache on Disk, indicating a higher cost of retrieval.
*
* @return true if an element matching the key is found in the diskStore
* @since 1.2
*/
boolean isElementOnDisk(Object key);
/**
* The GUID for this cache instance can be used to determine whether two cache instance references
* are pointing to the same cache.
*
* @return the globally unique identifier for this cache instance. This is guaranteed to be unique.
* @since 1.2
*/
String getGuid();
/**
* Gets the CacheManager managing this cache. For a newly created cache this will be null until
* it has been added to a CacheManager.
*
* @return the manager or null if there is none
*/
CacheManager getCacheManager();
/**
* Resets statistics counters back to 0.
*/
void clearStatistics();
/**
* Accurately measuring statistics can be expensive. Returns the current accuracy setting.
*
* @return one of {@link Statistics#STATISTICS_ACCURACY_BEST_EFFORT}, {@link Statistics#STATISTICS_ACCURACY_GUARANTEED}, {@link Statistics#STATISTICS_ACCURACY_NONE}
*/
public int getStatisticsAccuracy();
/**
* Sets the statistics accuracy.
*
* @param statisticsAccuracy one of {@link Statistics#STATISTICS_ACCURACY_BEST_EFFORT}, {@link Statistics#STATISTICS_ACCURACY_GUARANTEED}, {@link Statistics#STATISTICS_ACCURACY_NONE}
*/
public void setStatisticsAccuracy(int statisticsAccuracy);
/**
* Causes all elements stored in the Cache to be synchronously checked for expiry, and if expired, evicted.
*/
void evictExpiredElements();
/**
* An inexpensive check to see if the key exists in the cache.
*
* @param key the key to check for
* @return true if an Element matching the key is found in the cache. No assertions are made about the state of the Element.
*/
boolean isKeyInCache(Object key);
/**
* An extremely expensive check to see if the value exists in the cache.
*
* @param value to check for
* @return true if an Element matching the key is found in the cache. No assertions are made about the state of the Element.
*/
boolean isValueInCache(Object value);
/**
* Gets an immutable Statistics object representing the Cache statistics at the time. How the statistics are calculated
* depends on the statistics accuracy setting. The only aspect of statistics sensitive to the accuracy setting is
* object size. How that is calculated is discussed below.
* <h3>Best Effort Size</h3>
* This result is returned when the statistics accuracy setting is {@link Statistics#STATISTICS_ACCURACY_BEST_EFFORT}.
* <p/>
* The size is the number of {@link Element}s in the {@link net.sf.ehcache.store.MemoryStore} plus
* the number of {@link Element}s in the {@link net.sf.ehcache.store.DiskStore}.
* <p/>
* This number is the actual number of elements, including expired elements that have
* not been removed. Any duplicates between stores are accounted for.
* <p/>
* Expired elements are removed from the the memory store when
* getting an expired element, or when attempting to spool an expired element to
* disk.
* <p/>
* Expired elements are removed from the disk store when getting an expired element,
* or when the expiry thread runs, which is once every five minutes.
* <p/>
* <h3>Guaranteed Accuracy Size</h3>
* This result is returned when the statistics accuracy setting is {@link Statistics#STATISTICS_ACCURACY_GUARANTEED}.
* <p/>
* This method accounts for elements which might be expired or duplicated between stores. It take approximately
* 200ms per 1000 elements to execute.
* <h3>Fast but non-accurate Size</h3>
* This result is returned when the statistics accuracy setting is {@link Statistics#STATISTICS_ACCURACY_NONE}.
* <p/>
* The number given may contain expired elements. In addition if the DiskStore is used it may contain some double
* counting of elements. It takes 6ms for 1000 elements to execute. Time to execute is O(log n). 50,000 elements take
* 36ms.
*
* @return the number of elements in the ehcache, with a varying degree of accuracy, depending on accuracy setting.
* @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
*/
Statistics getStatistics() throws IllegalStateException;
/**
* Sets the CacheManager
*
* @param cacheManager the CacheManager for this cache to use.
*/
void setCacheManager(CacheManager cacheManager);
/**
* Accessor for the BootstrapCacheLoader associated with this cache. For testing purposes.
*
* @return the BootstrapCacheLoader to use
*/
BootstrapCacheLoader getBootstrapCacheLoader();
/**
* Sets the bootstrap cache loader.
*
* @param bootstrapCacheLoader the loader to be used
* @throws CacheException if this method is called after the cache is initialized
*/
void setBootstrapCacheLoader(BootstrapCacheLoader bootstrapCacheLoader) throws CacheException;
/**
* DiskStore paths can conflict between CacheManager instances. This method allows the path to be changed.
*
* @param diskStorePath the new path to be used.
* @throws CacheException if this method is called after the cache is initialized
*/
void setDiskStorePath(String diskStorePath) throws CacheException;
/**
* Newly created caches do not have a {@link net.sf.ehcache.store.MemoryStore} or a {@link net.sf.ehcache.store.DiskStore}.
* <p/>
* This method creates those and makes the cache ready to accept elements
*/
void initialise();
/**
* Bootstrap command. This must be called after the Cache is intialised, during
* CacheManager initialisation. If loads are synchronous, they will complete before the CacheManager
* initialise completes, otherwise they will happen in the background.
*/
void bootstrap();
/**
* Flushes all cache items from memory to auxilliary caches and close the auxilliary caches.
* <p/>
* Should be invoked only by CacheManager.
*
* @throws IllegalStateException if the cache is not {@link Status#STATUS_ALIVE}
*/
public void dispose() throws IllegalStateException;
/**
* Gets the cache configuration this cache was created with.
* <p/>
* Things like listeners that are added dynamically are excluded.
*/
CacheConfiguration getCacheConfiguration();
/**
* Register a {@link CacheExtension} with the cache. It will then be tied into the cache lifecycle.
* <p/>
* If the CacheExtension is not initialised, initialise it.
*/
public void registerCacheExtension(CacheExtension cacheExtension);
/**
* Unregister a {@link CacheExtension} with the cache. It will then be detached from the cache lifecycle.
*/
public void unregisterCacheExtension(CacheExtension cacheExtension);
/**
*
* @return the cache extensions as a live list
*/
public List<CacheExtension> getRegisteredCacheExtensions();
/**
* The average get time in ms.
*/
public float getAverageGetTime();
/**
* Sets an ExceptionHandler on the Cache. If one is already set, it is overwritten.
*/
public void setCacheExceptionHandler(CacheExceptionHandler cacheExceptionHandler);
/**
* Sets an ExceptionHandler on the Cache. If one is already set, it is overwritten.
*/
public CacheExceptionHandler getCacheExceptionHandler();
/**
* Register a {@link CacheLoader} with the cache. It will then be tied into the cache lifecycle.
* <p/>
* If the CacheLoader is not initialised, initialise it.
*
* @param cacheLoader A Cache Loader to register
*/
public void registerCacheLoader(CacheLoader cacheLoader);
/**
* Unregister a {@link CacheLoader} with the cache. It will then be detached from the cache lifecycle.
*
* @param cacheLoader A Cache Loader to unregister
*/
public void unregisterCacheLoader(CacheLoader cacheLoader);
/**
*
* @return the cache loaders as a live list
*/
public List<CacheLoader> getRegisteredCacheLoaders();
/**
* This method will return, from the cache, the object associated with
* the argument "key".
* <p/>
* If the object is not in the cache, the associated
* cache loader will be called. That is either the CacheLoader passed in, or if null, the one associated with the cache.
* If both are null, no load is performed and null is returned.
* <p/>
* Because this method may take a long time to complete, it is not synchronized. The underlying cache operations
* are synchronized.
*
* @param key key whose associated value is to be returned.
* @param loader the override loader to use. If null, the cache's default loader will be used
* @param loaderArgument an argument to pass to the CacheLoader.
* @return an element if it existed or could be loaded, otherwise null
* @throws CacheException
*/
public Element getWithLoader(Object key, CacheLoader loader, Object loaderArgument) throws CacheException;
/**
* The getAll method will return, from the cache, a Map of the objects associated with the Collection of keys in argument "keys".
* If the objects are not in the cache, the associated cache loader will be called. If no loader is associated with an object,
* a null is returned. If a problem is encountered during the retrieving or loading of the objects, an exception will be thrown.
* If the "arg" argument is set, the arg object will be passed to the CacheLoader.loadAll method. The cache will not dereference
* the object. If no "arg" value is provided a null will be passed to the loadAll method. The storing of null values in the cache
* is permitted, however, the get method will not distinguish returning a null stored in the cache and not finding the object in
* the cache. In both cases a null is returned.
* <p/>
* <p/>
* Note. If the getAll exceeds the maximum cache size, the returned map will necessarily be less than the number specified.
* <p/>
* Because this method may take a long time to complete, it is not synchronized. The underlying cache operations
* are synchronized.
* <p/>
* The constructs package provides similar functionality using the
* decorator {@link net.sf.ehcache.constructs.blocking.SelfPopulatingCache}
* @param keys a collection of keys to be returned/loaded
* @param loaderArgument an argument to pass to the CacheLoader.
* @return a Map populated from the Cache. If there are no elements, an empty Map is returned.
* @throws CacheException
*/
public Map getAllWithLoader(Collection keys, Object loaderArgument) throws CacheException;
/**
* The load method provides a means to "pre load" the cache. This method will, asynchronously, load the specified
* object into the cache using the associated cacheloader. If the object already exists in the cache, no action is
* taken. If no loader is associated with the object, no object will be loaded into the cache. If a problem is
* encountered during the retrieving or loading of the object, an exception should be logged. If the "arg" argument
* is set, the arg object will be passed to the CacheLoader.load method. The cache will not dereference the object.
* If no "arg" value is provided a null will be passed to the load method. The storing of null values in the cache
* is permitted, however, the get method will not distinguish returning a null stored in the cache and not finding
* the object in the cache. In both cases a null is returned.
* <p/>
* The Ehcache native API provides similar functionality to loaders using the
* decorator {@link net.sf.ehcache.constructs.blocking.SelfPopulatingCache}
*
* @param key key whose associated value to be loaded using the associated cacheloader if this cache doesn't contain it.
* @throws CacheException
*/
public void load(final Object key) throws CacheException;
/**
* The loadAll method provides a means to "pre load" objects into the cache. This method will, asynchronously, load
* the specified objects into the cache using the associated cache loader. If the an object already exists in the
* cache, no action is taken. If no loader is associated with the object, no object will be loaded into the cache.
* If a problem is encountered during the retrieving or loading of the objects, an exception (to be defined)
* should be logged. The getAll method will return, from the cache, a Map of the objects associated with the
* Collection of keys in argument "keys". If the objects are not in the cache, the associated cache loader will be
* called. If no loader is associated with an object, a null is returned. If a problem is encountered during the
* retrieving or loading of the objects, an exception (to be defined) will be thrown. If the "arg" argument is set,
* the arg object will be passed to the CacheLoader.loadAll method. The cache will not dereference the object.
* If no "arg" value is provided a null will be passed to the loadAll method.
* <p/>
* keys - collection of the keys whose associated values to be loaded into this cache by using the associated
* cacheloader if this cache doesn't contain them.
* <p/>
* The Ehcache native API provides similar functionality to loaders using the
* decorator {@link net.sf.ehcache.constructs.blocking.SelfPopulatingCache}
*/
public void loadAll(final Collection keys, final Object argument) throws CacheException;
/**
* Whether this cache is disabled. "Disabled" means:
* <ol>
* <li>bootstrap is disabled
* <li>puts are discarded
* <li>putQuites are discarded
* </ol>
* In all other respects the cache continues as it is.
* <p/>
* You can disable and enable a cache programmatically through the {@link #setDisabled(boolean)} method.
* <p/>
* @return true if the cache is disabled.
*/
public boolean isDisabled();
/**
* Disables or enables this cache. This call overrides the previous value of disabled.
* <p/>
* @param disabled true if you wish to disable, false to enable
* @see #isDisabled()
*/
public void setDisabled(boolean disabled);
}
Ehcache使用实例(二)
Cache使用
l 得到一个Cache引用
获得一个sampleCache1的引用,从官方下载ehcache.xml,在ehcache.xml中已经有配置好的缓存,大家直接使用就可以,或是做测试,如果说真正使用的时候,最后自己手动配置一个比较好。
Cache cache = manager.getCache(“sampleCache1”);
l 使用Cache
Put一个Element到cache中
Cache cache = manager.getCache(“sampleCache1”);
Element element = new Element(“key1”,”value1”);
cache.put(element);
update一个element时,只要在构造element时将相同的key传入,在调用cache.put(element),这是Ehcache会根据key到缓存中找到对应的element并做更新。
Cache cache = manager.getCache(“sampleCache1”);
Cache.put(new Element(“key1”, “value1”));
//更新element
Cache.put(new Element(“key1”, “value2”));
根据key取得对应element的序列化value值
Cache cache = manager.getCache(“sampleCache1”);
Element element = cache.get(“key1”);
Serializable value = element.getValue();
根据key取得对应element的非序列化value值
Cache cache = manager.getcache(“samplecache1”);
Element element = cache.get(“key1”);
Ojbect value = element.getObjectValue();
http://blog.csdn.net/mgoann/archive/2009/04/22/4099190.aspx
Ehcache使用用例(一)
Singleton创建方式
Ehcache1.2版本之后,都可以使用singleton(工厂创建方法)去创建一个singleton的CacheManager实例。
CacheManager.create();
String[] cacheNames = CacheManager.getInstance().getCacheNames();
使用默认配置创建CacheManager
CacheManager manager = new CacheManager();
String[] cacheNames = manager.getCacheNames();
使用配置文件创建指定的CacheManager
CacheManager manager1 = new CacheManager(“src/config/ehcache1.xml”);
CacheManager manager2 = new CacheManager(“src/config/ehcache2.xml”);
String[] cacheNamesForManager1 = manager1.getCacheNames();
String[] cacheNamesForManager2 = manager2.getCacheNames();
配置文件加载方式
· CacheManager manager = new CacheManager();会在classpath路径下找ehcache.xml配置文件。
· CacheManager manager = new CacheManager(“src/config.ehcache.xml”); 也可以根据相对文件路径来加载配置文件.
· 通过URL加载
URL url = getClass().getResource(“/anotherconfigurationname.xml”);
CacheManager manager = new CacheManager(url);
· 通过流加载
InputSream fis = new FileInputStream(new File(“src/config/ehcache.xml”).getAbsolutePath());
Try {
CacheManager manager = new CacheManager(fis);
} finally {
Fis.close();
}
编码实现添加和缓存
Ehcache中不仅可以用配置文件来配置缓存,而在代码中也可以实现同样的功能。
CacheManager singletonManager = CacheManager.create();
Cache memoryOnlyCache = new Cache(“testCache”, 50000, false, false, 8, 2);
Cache test = singletonManager.getCache(“testCache”);
删除只需要调用singletonManager.removeCache(“testCache”);
Shotdown CacheManager
在使用完Ehcache后,必须要shutdown缓存。Ehcache中有自己的关闭机制,不过最好在你的代码中显示调用CacheManager.getInstance().shutdown();
http://blog.csdn.net/mgoann/archive/2009/04/20/4095155.aspx
Ehcache Storage Options http://blog.csdn.net/mgoann/archive/2009/04/20/4094768.aspx
简介
Ehcache俩中缓存机制:
· MemoryStore(内存存储)
· DiskStore(磁盘存储)
MemoryStore
MemoryStore总是可用的,但不可直接操作,当中存储着所有的Cache。
· 合适的Element类型
所有的Element都可以放在MemoryStore中。
· 安全性:使用多个线程并行检查内存泄露。
· JDK:使用了JDK1.5的LinkedHashMap来存放Elemen。t
· 快速的:是最快的缓存机制,因为它是存放在内存中。
· 失效策略(Memory)
Cache可以配置最大缓存Element的个数,以及失效时间。如果在添加Elemtent时,缓存中的Element个数达到了最大缓存数并且overflowToDisk配置的属性为true,Ehcache会更具配置项MemoryStoreEvictionPolicy的失效策略将Element输出到磁盘。如果overflowToDisk为fasle,Ehcache将删除内存中Element。Ehcache支持三种失效策略:LRU、LFU、FIFO。
值得注意的是缓存中失效的Element并不会别马上清理掉,所以想得到内存的真实大小应该调用方法calculateInMemorySize()方法。
DiskStore
DiskStore可缓存到外部设备上(硬盘)。
· DiskStores are Optional
Ehcache从1.5版本开始支持DiskStore。如果你需要多个DiskStore的话,最好给他们配置不同的文件路径。
· 关闭磁盘缓存:只要注释掉ehcache.xml配置文件中的磁盘缓存配置项即可。而ehcache-failsafe.xml的磁盘缓存配置不会影响到你自己的cache。
· 合适的Element类型
这里要注意要想使用磁盘缓存,缓存的Element必须实现序列化接口。否则会抛出NotSerializableException异常。
· 存储:Ehcache会将每个缓存配置的文件路径下创建一个cache_name.data文件,如果使用的磁盘持久化技术,还会生成一个cache name.index文件。
· 失效
Ehcache有一个后天线程专门做Ellment失效监测以及清除工作。设置线程运行间隔时间,可通过设置diskExpiryThreadIntervalSeconds属性来完成,此值不宜设置过低,否则会导致清理线程占用大量CPU资源。默认值是120秒。
· 持久化
持久化可在Element的diskPersistent配置项中配置,如果配置为“false”或是“omitted”在CacheManager shutdown或是startup后,用来缓存Element的文件将被清除掉。如果设置为“true”,data和index文件会被保存下来,对于新创建的CacheManager Element也是可用的。
使用时必须显示调用cache. Flush()才会将数据缓存到磁盘中。
磁盘缓存步骤:从MemoryStore中把没有失效的Element刷新到DiskStore,Element被写入到data文件,Element将被序列化到index文件。
Ehcache缓存回收策略
简介
缓存回收就是当缓存满了的时候,Ehcache会根据指定的策略来清理缓存。这里的缓存回收策略有别与Ehcache中的失效清理策略。失效清理策略是对失效的Element进行批量清理时所采用的策略,最终所有失效的Element都将被清理,只不过是清理的先后顺序不同罢了。Ehcache中缓存的最大Element数是由maxElementsInMemory来指定的。当缓存中的Element个数达到maxElementsInMemory指定的值时,Ehcache会根据具体的策略来清理缓存,默认的策略是LRU(最近最少使用)。
磁盘缓存大小默认是没有限制的,不过可通过maxElementsOnDisk来指定。当磁盘缓存达到maxElementsOnDisk指定的值时,Ehcache会清理磁盘中的缓存使用默认策略是LFU(使用频率最低)。
MemoryStore回收策略
MemoryStore支持三种策略:LRU、LFU、FIFO。
Ehcache缓存配置 http://blog.csdn.net/mgoann/archive/2009/04/17/4087714.aspx
简介
Cache的配置很灵活,官方提供的Cache配置方式有好几种。你可以通过声明配置、在xml中配置、在程序里配置或者调用构造方法时传入不同的参数。
你可以将Cache的配置从代码中剥离出来,也可以在使用运行时配置,所谓的运行时配置无非也就是在代码中配置。以下是运行时配置的好处:
· 在同一个地方配置所有的Cache,这样很容易管理Cache的内存和磁盘消耗。
· 发布时可更改Cache配置。
· 可再安装阶段就检查出配置错误信息,而避免了运行时错误。
本文将会对ehcache.xml配置文件进行详细的阐述。在配置的时可以拷贝一个现有的ehcache.xml,如果没有请点击这里去下载。
ehcache-failsafe.xml
如果你调用了CacheManager默认构造方法去创建CacheManager的实例,此方法会到classpath中找ehcache.xml文件,否则它会到类路径下找ehcache-failsafe.xml文件。而ehcache-failsafe.xml被包含在jar包中,所有它肯定能找的到。
ehcache-failsafe.xml提供了一个非常简单的默认配置,这样可以使用户在没有创建ehcache.xml的情况下使用Ehcache。
不过这样做Ehcache会提醒用户创建一个正确的Ehcache配置。
ehcache.xml片段:
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>
ehcache.xml和其他配置文件
在Ehcache-1.6之前的版本,只支持ASCII编码的ehcache.xml配置文件。在Ehcach-1.6之后版本中,支持UTF8编码的ehcache.xml配置文件。因为向后兼容,所有采用ASCII编码的配置文件完全没有必要转换为UTF8。
一个CacheManager必须要有一个XML配置。由于磁盘路径或是监听端口,多个CacheManager使用同一个配置文件时会出现错误。
下面是ehcache.xml具体实例以及配置指南
<ehcache xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
· CacheManager配置
DmulticastGroupPort=4446,这样可以配置监听端口。
· DiskStore配置
如果你使用的DiskStore(磁盘缓存),你必须要配置DiskStore配置项。如果不配置,Ehcache将会使用java.io.tmpdir。
diskStroe的“path”属性是用来配置磁盘缓存使用的物理路径的,Ehcache磁盘缓存使用的文件后缀名是.data和.index。
<disStore path=”java.io.tmpdir”/>
· CacheManagerEventListener配置
我们通过CacheManagerEventListenerFactory可以实例化一个CacheManagerPeerProvider,当我们从CacheManager中added和removed Cache时,将通知CacheManagerPeerProvider,这样一来,我们就可以很方面的对CacheManager中的Cache做一些统计。
注册到CacheManager的事件监听类名有: adding a Cache和removing a Cache
<cacheManagerEventListenerFacotory class=”” properties=””/>
· CacheManagerPeerProvider配置
在集群中CacheManager配置CacheManagerPeerProviderFactory创建CacheManagerPeerProvider。具体的实例如下:
<cacheManagerPeerProviderFactoryclass="net.sf.ehcache.distribution.
RMICacheManagerPeerProviderFactory"
properties="peerDiscovery=manual, rmiUrls=//server1:40000/sampleCache1|//server2:40000/sampleCache1| //server1:40000/sampleCache2|//server2:40000/sampleCache2"
propertySeparator="," />
· CacheManagerPeerListener配置
CacheManagerPeerListener配置是用来监听集群中缓存消息的分发的。
<cacheManagerPeerListenerFactory
class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
properties="hostName=fully_qualified_hostname_or_ip,
port=40001,
socketTimeoutMillis=120000"
propertySeparator="," />
· Cache配置
· name:Cache的唯一标识
· maxElementsInMemory:内存中最大缓存对象数。
· maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大。
· eternal:Element是否永久有效,一但设置了,timeout将不起作用。
· overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中。
· timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
· timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大。
· diskPersistent:是否缓存虚拟机重启期数据。(这个虚拟机是指什么虚拟机一直没看明白是什么,有高人还希望能指点一二)。
· diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
· diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
· memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。这里比较遗憾,Ehcache并没有提供一个用户定制策略的接口,仅仅支持三种指定策略,感觉做的不够理想。
· Cache Exception Handling配置
<cacheExceptionHandlerFactory class="com.example.ExampleExceptionHandlerFactory" properties="logLevel=FINE"/>
总结
这里只对通用缓存的配置做了详细的阐述,至于RMI缓存和集群缓存可以参考这里。
下面给出几个配置示例:
· Ehcache默认Cache配置
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskSpoolBufferSizeMB="30"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
· SampleCache1配置
简单配置,在ehcache.xml文件中有此配置,在使用Ehcache前最好将其删除掉,自己配置。
缓存名sampleCache1,内存中最多可缓存10000个Element,其中的element会在闲置
简介 http://blog.csdn.net/mgoann/archive/2009/04/17/4086641.aspx
缓存有多种不同的缓存模式。以下是Ehcache支持的缓存模式:
· 直接操作(direct manipulation)
· 推送模式(pull-through)
· 自填充(self-populating)
直接操作(direct manipulation)
你可以通过方法cache.put(Elemtn element)来存储对象,通过方法cache.get(Ojbect key)来取得对象。
自填充(self-populating)
只需要使用cache.get(Ojbect key)来取得对象。Cache自动去构建实体。
http://blog.csdn.net/mgoann/archive/2009/04/17/4086534.aspx
Ehcache关键类
简介
Ehcache中的CacheManager是用来管理Cache的。而Cache中包含元素Element,而Element实质上就是一个键值对。Cache在物理方面的实现有内存实现和磁盘实现。
CacheManager
CacheManager包含Cache,而Cache反过来构成了CacheManager的要素。
· CacheManager创建模式:CacheManger创建模式包含singleton和instance俩种。
· Simgleton Mode
从字面可理解为单例模式,这种模式只允许创建一个CacheManger实例。
· Instance Mode
那这种模式从字面上理解也应该是实例化模式了。从Ehcache-1.2以来,CacheManager有多种静态创建方法。这样我们就可以同时创建多个复杂的不同配置的CacheManager。
如果只是用内存来存储Cache,那么没有什么需要你特别考虑的。如果是用磁盘来缓存Cache的话,你就必须为CacheManager指定的磁盘路径。当一个新的CacheManager被创建时,必须要确保磁盘路径没有被别的CacheManager使用。如果重复使用磁盘路径,这个时候就会抛出CacheException。如果CacheManger配置成集群模式的,还要注意端口的指定。
· Singletion和Instance混用
如果一个应用程序通过构造方法创建了一个CacheManager的实例,也调用了静态创建方法,这个时候会,没调用一次静态创建方法就会返回一个CacheManager的Singleton实例,所有的这些实例将会共存。
Ehcache
Ehcache是一个接口,所有的Cache都实现了Ehcache。每个Cache都有自己的名字和特定的属性以及包含着Element元素。
Ehcache中的Cache代表一块特定的缓存区域或是缓存系统。
Cache的Element元素可以存储到MemoryStore(内存)中,也可以写到DiskStore(磁盘)中。
Element
每一个Element对应一个缓存的原子实体。它有key、value以及访问记录属性。Element可以被put进Cache也可以remove出Cache。通过配置Cache可定制Element的失效以及移除策略。
在Ehcache-1.2 API中要求存储对象要可序列化。没有序列化的对象不可以存储到DiskStore(磁盘)中,也不能被拷贝。
在Element类中注意到有俩个方法getOjbectValue和getKeyValue,这个方法都是从缓存中取得Element元素的方法,唯一区别就是一个是用来取得序列化对象,另外一个是不可序列化对象。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/mgoann/archive/2009/04/17/4086534.aspx
入门指南http://blog.csdn.net/mgoann/archive/2009/04/16/4083962.aspx
Ehcache Getting Started
简介
Ehcache可以直接使用。也可以和Hibernate对象/关系框架结合使用。还可以做Servlet缓存。
通用缓存
· 确保JDK版本支持你现有的Ehcache版本,Ehcache支持JDK1.4和1.5以及1.6版本。
· Ehcache jar包加入你的classpath环境变量中。
· Ehcache依赖的类库加入到你的classpath环境变量中,不同版本的Ehcache依赖类库不尽相同,请注意的Ehcache版本以及相关依赖类库,这里不做累述,可以参考
http://ehcache.sourceforge.net/documentation/dependencies.html
· 配置ehcache.xml配置文件,并加入到classpath中。
· 配置logging到合适的级别。
Hibernate
· 参考通用缓存的步骤。
· 在ehcache.xml中创建缓存。
Java EE Servlet缓存
· 参考通用缓存步骤。
· 在ehcache.xml中为你的web页面配置缓存。
· 如果要缓存全部页面,可以使用Ehcache提供的SimplePageCachingFilter或是自己写一个子类继承CachingFilter。
· 若要缓存某个具体的JSP页面(包括由RequestDispatcher返回的页面),你可以使用Ehcache提供的SimplePageFragmentCachingFilter或是写个子类去继承PageFragmentCachingFilter。
· 配置web.xml。这里就相对简单一些,只是将你上俩个步骤使用的Filter配置到web.xml中,以便请求再此到来时能够访问缓存中的页面,从而达到提供页面的相应速度。
RESTful和SOAP缓存
· 从http://sourceforge.net/project/showfiles.php?group_id=93232下载缓存服务端。
· 使用cd命令切换到bin目录下。
· 键入startup.sh启动服务。
默认情况下使用的是8080端口,RESTful和SOAP web服务器也都使用这个端口,请注意端口占用问题。
· 这个时候就可以使用Cache Server了,你可以使用Java后者任何其他语言。具体示例请参考
http://ehcache.sourceforge.net/documentation/cache_server.html。
Jcache style caching
Ehcache在net.sh.ehcache.jcache这个包下,有Ehcache早期对Jcache的一个粗略实现。
Spring,Cocoon,Acegi和其他框架
一般,和这些框架结合使用Ehcache时,没有太多特殊的地方。你只需要注意以下几点:
· 注意这些框架中使用的是什么缓存。
· 创建ehcache.xml,配置缓存再放入你的classpath路径下。
Ehcache
简介
Ehcache是一种广泛使用java分布式缓存的通用缓存,J2EE和轻量级容器。
它具有内存和磁盘缓存,副本的copy和失效,监听,缓存装载,扩展缓存,缓存异常处理,gzip缓存servlet过滤器,RESTful&SOAP API等特性。
Ehcache下提供Apache开源许可证并在积极的开发、维护和支持。
特性
· 高效且轻量级
· 快速
· 简单
· 低消耗
· 依赖性小
· 扩展性
· 提供内存和磁盘缓存,可扩展到数亿bytes
· 可扩展数百种缓存
· 适合高负载多核CPU
· 可以为每个虚拟机创建多个复杂的CacheManager
· 灵活性
· 支持对象或序列化缓存
· 支持缓存或元素的失效
· 提供LRU、LFU和FIFO缓存策略
· 支持内存缓存和磁盘缓存
· 分布式缓存机制
· 基于标准
· 完全实现JSR107 JCACHE API
· 扩展性
· 插件式
· 插件式查询、克隆和监测
· 插件式缓存扩展
· 插件式缓存异常处理
· 持久化应用
· 持久化磁盘存储JVM重启期数据。
· 可控磁盘缓存
· 支持监测
· CacheManager监测
· 缓存时间监测
· JMX支持
· 分布式
· 支持RMI、Jgroups、JMS或Terracotta深层拷贝
· 预查询
· 可靠分发机制
· 同步异步拷贝
· 复制或是无效拷贝
· 事务拷贝
· 扩展性
· 缓存服务
· RESTful缓存服务
· SOAP缓存服务
· Java EE高速缓存
· 高速缓存拦截机制避免并发操作的重复处理
· 为耗时操作提供自我缓存
· Java EE Gzip Servlet过滤
· 指令集缓存
· 兼容Hibernate
· 高性能
· 高覆盖率测试
· 自动化负载、边界、性能系统测试
· 产品级测试
· 完整的文档
· Popular框架组织信赖
· 开放源码证书
· Apache2.0许可证