一、OSCache提供的缓存标签
这是OSCache提供的标签库中最重要的一个标签,包括在标签中的内容将应用缓存机制进行处理,处理的方式将取决于编程者对cache标签属性的设置。
第一次请求到达时,标签中的内容被处理并且缓存起来,当下一个请求到达时,缓存系统会检查这部分内容的缓存是否已经失效,主要是以下几项:
1. 缓存时间超过了cache标签设置的time或者duration属性规定的超时时间;
2. cron属性规定的时间比缓存信息的开始时间更晚;
3. 标签中缓存的内容在缓存后又被重新刷新过;
4. 其他缓存超期设定。
如果符合上面四项中的任何一项,被缓存的内容视为已经失效,这时被缓存的内容将被重新处理并且返回处理过后的信息,如果被缓存的内容没有失效,那么返回给用户的将是缓存中的信息。
cache标签的属性说明:
key - 标识缓存内容的关键词。在指定的作用范围内必须是唯一的。默认的key是被访问页面的URI和后面的请求字符串。
你可以在同一个页面中使用很多cache标签而不指定他的key属性,这种情况下系统使用该页面的URI和后面的请求字符串,另外再自动给这些key增加一个索引值来区分这些缓存内容。但是不推荐采用这样的方式。
scope - 缓存发生作用的范围,可以是application或者session。
time - 缓存内容的时间段,单位是秒,默认是3600秒,也就是一个小时,如果设定一个负值,那么这部分被缓存的内容将永远不过期。
duration - 指定缓存内容失效的时间,是相对time的另一个选择,可以使用简单日期格式或者符合USO-8601的日期格式。
refresh - false 或者true。
如果refresh属性设置为true,不管其他的属性是否符合条件,这部分被缓存的内容都将被更新,这给编程者一种选择,决定什么时候必须刷新。
mode - 如果编程者不希望被缓存的内容增加到给用户的响应中,可以设置mode属性为"silent"。
其它可用的属性还包括:cron 、groups、language、refreshpolicyclass、refreshpolicyparam。
上面的这些属性可以单独使用,也可以根据需要组合使用,下面的例子将讲解这些常用属性的使用方式。
二、Cache标签实例分析
1. 最简单的cache标签用法
使用默认的关键字来标识cache内容,超时时间是默认的3600秒
<cache:cache>
<% //自己的JSP代码内容 %>
</cache:cache>
2. 用自己指定的字符串标识缓存内容,并且设定作用范围为session。
<cache:cache key="foobar" scope="session">
<% //自己的JSP代码内容 %>
</cache:cache>
3.动态设定key值,使用自己指定的time属性设定缓存内容的超时时间,使用动态refresh值决定是否强制内容刷新。
因为OSCache使用key值来标识缓存内容,使用相同的key值将会被认为使用相同的的缓存内容,所以使用动态的key值可以自由的根据不同的角色、不同的要求决定使用不同的缓存内容。
<cache:cache key="<%= product.getId() %>" time="1800" refresh="<%= needRefresh %>">
<% //自己的JSP代码内容 %>
</cache:cache>
4. 设置time属性为负数使缓存内容永不过期
<cache:cache time="-1">
<% //自己的JSP代码内容 %>
</cache:cache>
5. 使用duration属性设置超期时间
<cache:cache duration=''PT5M''>
<% //自己的JSP代码内容 %>
</cache:cache>
6. 使用mode属性使被缓存的内容不加入给客户的响应中
<cache:cache mode=''silent''>
<% //自己的JSP代码内容 %>
</cache:cache>
案例一
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@page import="java.text.SimpleDateFormat"%> <%@ taglib uri="http://www.opensymphony.com/oscache" prefix="cache"%> <html> <body> 没有缓存的日期: <%=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())%><p> <!--自动刷新--> <cache:cache time="30"> 每30秒刷新缓存一次的日期: <%=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())%><p> </cache:cache> <!--手动刷新--> <cache:cache key="testcache"> 手动刷新缓存的日期: <%=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())%><p> </cache:cache> <a href="cache2.jsp">手动刷新</a> </body> </html>
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib uri="http://www.opensymphony.com/oscache" prefix="cache"%> <html> <body> 缓存已刷新... <cache:flush key="testcache" scope="application" /> <a href="cache1.jsp">返回</a> </body> </html>
三、缓存过滤器 CacheFilter
你可以在web.xml中定义缓存过滤器,定义特定资源的缓存。
<filter> <filter-name>CacheFilter</filter-name> <filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class> <init-param> <param-name>time</param-name> <param-value>60</param-value> </init-param> <init-param> <param-name>scope</param-name> <param-value>session</param-value> </init-param> </filter> <filter-mapping> <filter-name>CacheFilter</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping>
上面定义将缓存所有.jsp页面,缓存刷新时间为60秒,缓存作用域为Session
注意,CacheFilter只捕获Http头为200的页面请求,即只对无错误请求作缓存,而不对其他请求(如500,404,400)作缓存处理。
如果在jsp中使用如下标签
<cache:cache key="foobar" scope="session"> some jsp content </cache:cache>
那么这中间的一段jsp代码将会以key="foobar"缓存在session中,任何其他页面中使用这个key的cache标签都能共享这段存在缓存中的执行结果。
考虑一个需求,一个页面是有许多个不同的jsp文件拼出来的,可能在页首有随机的广告,登录用户的信息,系统的即时信息,固定的目录信息等等;这其中可以考虑将固定的目录信息放入缓存中,而其他动态信息则即时刷新;再进一步考虑有时候页面之间的信息是关联的,只有当其中一条信息的内容变化了才需要去刷新。
对于这种需求就可以考虑在<cache:cache/>标签中配置group属性,将不同的具有关联关系的cache内容分组,这样oscache会自动的帮你检查该组缓存内容的变化情况,如果有任何一子成员组的内容变化了则会执行刷新,这样就可以在页面实现数据的动态同步。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ page import="java.text.SimpleDateFormat"%> <%@ taglib uri="http://www.opensymphony.com/oscache" prefix="cache"%> <head> <title>Test Page</title> <style type="text/css"> body { font-family: Arial, Verdana, Geneva, Helvetica, sans-serif } </style> </head> <body> <a href="<%=request.getContextPath()%>/">Back to index</a> <p> <hr> Flushing 'group2' <hr> <cache:flush group='group2' scope='application' /> <hr> <!-- 这里有两个cache分组group1和group2,将group2设置为每次都执行刷新,所以test1为key的cache每次刷新页面内容都是重新执行过的 --> <cache:cache key='test1' groups='group1,group2' duration='5s'> <b>Cache Time1</b>: <%=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())%><br> This is some cache content test1 that is in 'group1' and 'group2'. Normally it would refresh if it was more than 5 seconds old, however the <cache:flush group='group2' scope='application' /> tag causes this entry to be flushed on every page refresh.<br> </cache:cache> <hr> <!-- test2只有当间隔时间超过5秒才会更新内容 --> <cache:cache key='test2' groups='group1' duration='5s'> <b>Cache Time2</b>: <%=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())%><br> This is some cache content test2 that is in 'group1' (refreshes if more than 5 seconds old)<br> </cache:cache> <hr> <!--每隔20秒刷新一次-> <cache:cache key='test3' duration='20s'> <b>Cache Time3</b>: <%=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())%><br> This is some cache content test3 that is in 'group1' and 'group2'. The groups are added using the tag.<br> </cache:cache> <hr> <!--实时刷新--> <cache:cache key='test4' duration='20s'> <b>Cache Time4</b>: <%=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())%><br> This is some cache content test4 that is in 'group1' and 'group2'. The groups are added using the tag.<br> <cache:addgroups groups='group1,group2' /> </cache:cache> <hr> </body> </html>
<cache:addgroup group='{you_group}'/>可以将所在的you_group加入当前所在位置的group中
用CashFilter实现页面级缓存。
在OSCache组件中提供了一个CacheFilter用于实现页面级的缓存,主要用于对web应用中的某些动态页面进行缓存,尤其是那些需要生成pdf格式文件/报表、图片文件等的页面,不仅减少了数据库的交互、减少数据库服务器的压力,而且对于减少web服务器的性能消耗有很显著的效果。
这种功能的实现是通过在web.xml中进行配置来决定缓存哪一个或者一组页面,而且还可以设置缓存的相关属性,这种基于配置文件的实现方式对于J2EE来说应该是一种标准的实现方式了。
[注]只有客户访问时返回http头信息中代码为200(也就是访问已经成功)的页面信息才能够被缓存。
1. 缓存单个文件
修改web.xml,增加如下内容,确定对/testContent.jsp页面进行缓存。
<filter> <filter-name>CacheFilter</filter-name> <filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class> </filter> <filter-mapping> <filter-name>CacheFilter</filter-name> <!-对/testContent.jsp页面内容进行缓存--> <url-pattern>/testContent.jsp</url-pattern> </filter-mapping>
2. 缓存URL pattern
修改web.xml,增加如下内容,确定对*.jsp页面进行缓存。
<filter> <filter-name>CacheFilter</filter-name> <filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class> </filter> <filter-mapping> <filter-name>CacheFilter</filter-name> <!-对所有jsp页面内容进行缓存--> <url-pattern>*.jsp</url-pattern> </filter-mapping>
3. 自己设定缓存属性
在页面级缓存的情况下,可以通过设置CacheFilter的初始属性来决定缓存的一些特性:time属性设置缓存的时间段,默认为3600秒,可以根据自己的需要只有的设置,而scope属性设置,默认为application,可选项包括application、session
<filter> <filter-name>CacheFilter</filter-name> <filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class> <init-param> <param-name>time</param-name> <param-value>600</param-value> </init-param> <init-param> <param-name>scope</param-name> <param-value>session</param-value> </init-param> </filter> <filter-mapping> <filter-name>CacheFilter</filter-name> <!-对所有jsp页面内容进行缓存--> <url-pattern>*.jsp</url-pattern> </filter-mapping>
在实际应用中除了JSP标签库,还可以使用OSCache提供的Java API.下面我来介绍一个实用的Java类,使用GeneralCacheAdministrator来建立,刷新和管理缓存.
GeneralCacheAdministrator类常用的方法有:
public Object getFromCache(String key) throws NeedsRefreshException; //从缓存中获取一个key标识的对象.
public Object getFromCache(String key, int refreshPeriod) throws NeedsRefreshException; //从缓存中获取一个key标识的对象. refreshPeriod刷新周期,标识此对象在缓存中保存的时间(单位:秒)
public void putInCache(String key, Object content); //存储一个由Key标识的缓存对象.
public void putInCache(String key, Object content, String[] groups); //存储一个由Key标识的属于groups中所有成员的缓存对象.
public void flushEntry(String key); //更新一个Key标识的缓存对象.
public void flushGroup(String group); //更新一组属于groupr标识的所有缓存对象.
public void flushAll(); //更新所有缓存.
public void cancelUpdate(String key); //取消更新,只用于在处理捕获的NeedsRefreshException异常并尝试生成新缓存内容失效的时候.
public void removeEntry(String key); //从缓存中移除一个key标识的对象
案例:
1、对象Bean
package com.ljq.test; import java.text.SimpleDateFormat; import java.util.Date; /** * 对象Bean * * @author 林计钦 * @version 1.0 Aug 16, 2013 7:50:06 PM */ public class User { private int id; private String name; private String sex; private int age; private Date accessTime; public User(int id) { super(); this.id = id; this.accessTime = new Date(); } public String toString() { String date=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(accessTime); return "User info is : id=" + id + " accessTime=" + date; } public User(String name, String sex, int age) { super(); this.name = name; this.sex = sex; this.age = age; } public User() { } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getId() { return id; } public void setId(int id) { this.id = id; } public Date getAccessTime() { return accessTime; } public void setAccessTime(Date accessTime) { this.accessTime = accessTime; } }
2、缓存操作类
package com.ljq.test; import com.opensymphony.oscache.base.NeedsRefreshException; import com.opensymphony.oscache.general.GeneralCacheAdministrator; /** * 缓存操作类 * * @author 林计钦 * @version 1.0 Aug 16, 2013 7:48:07 PM */ public class BaseCache extends GeneralCacheAdministrator { private static final long serialVersionUID = 6239736145696260016L; private int refreshPeriod; // 过期时间(单位为秒) private String keyPrefix; // 关键字前缀字符 public BaseCache(String keyPrefix, int refreshPeriod) { super(); this.keyPrefix = keyPrefix; this.refreshPeriod = refreshPeriod; } /** * 添加被缓存的对象 * * @param key * @param value */ public void put(String key, Object value) { this.putInCache(this.keyPrefix + "_" + key, value); } /** * 删除被缓存的对象 * * @param key */ public void remove(String key){ this.removeEntry(this.keyPrefix + "_" + key); } /** * 删除所有被缓存的对象 */ public void removeAll(){ this.flushAll(); } /** * 获取被缓存的对象 * * @param key * @return * @throws Exception */ public Object get(String key) throws NeedsRefreshException { return this.getFromCache(this.keyPrefix + "_" + key, this.refreshPeriod); } public void cancel(String key){ this.cancelUpdate(key); } public void put(String key, Object value, String[] groups) { this.putInCache(this.keyPrefix + "_" + key, value, groups); } /** * 删除该组的缓存对象 * * @param group */ public void removeObjectByGroup(String group) { this.flushGroup(group); } }
3、Cache管理类
package com.ljq.test; /** * Cache管理类 * * @author 林计钦 * @version 1.0 Aug 16, 2013 7:49:35 PM */ public class CacheManager { private boolean update=false; private BaseCache userCache; private static CacheManager instance; private static Object lock = new Object(); private CacheManager() { // 这个根据配置文件来,初始BaseCache而已; userCache = new BaseCache("user", 120); } public static CacheManager getInstance() { if (instance == null) { synchronized (lock) { if (instance == null) { instance = new CacheManager(); } } } return instance; } public void putUser(User user) { userCache.put(user.getId() + "", user); } public void removeUser(String id) { userCache.remove(id); } public void removeAllUser() { userCache.removeAll(); } public User getUser(int id) { try { //从Cache中获得 return (User) userCache.get(id + ""); } catch (Exception e) { //Cache中没有则从DB库获取 System.out.println(String.format("[%s]从db中获取", id+"")); User user = new User(id); //把获取的对象再次存入Cache中 this.putUser(user); update=true; return user; }finally{ if(!update){ //如果Cache中的内容更新出现异常,则终止该方法 userCache.cancel(id + ""); //取消对id的更新 } } } }
4、测试类
package com.ljq.test; /** * 测试类 * * @author 林计钦 * @version 1.0 Aug 16, 2013 7:51:19 PM */ public class UserCacheTest { public static void main(String[] args) { CacheManager cm = CacheManager.getInstance(); UserCacheTest test = new UserCacheTest(); test.print(cm); } public void print(CacheManager cm) { User user = null; for (int i = 0; i < 20; i++) { user = cm.getUser(100); System.out.println("<<" + i + ">>: " + user); if (i == 5) { // 删除缓存id的对象 cm.removeUser(100 + ""); } if (i == 10) { // 删除所有缓存的对象 cm.removeAllUser(); } // 睡眠部分 try { Thread.sleep(1000); } catch (Exception e) { } } } }
5、执行后控制台打印的结果
[100]从db中获取 <<0>>: User info is : id=100 accessTime=2013-08-16 21:20:27 <<1>>: User info is : id=100 accessTime=2013-08-16 21:20:27 <<2>>: User info is : id=100 accessTime=2013-08-16 21:20:27 <<3>>: User info is : id=100 accessTime=2013-08-16 21:20:27 <<4>>: User info is : id=100 accessTime=2013-08-16 21:20:27 <<5>>: User info is : id=100 accessTime=2013-08-16 21:20:27 [100]从db中获取 <<6>>: User info is : id=100 accessTime=2013-08-16 21:20:33 <<7>>: User info is : id=100 accessTime=2013-08-16 21:20:33 <<8>>: User info is : id=100 accessTime=2013-08-16 21:20:33 <<9>>: User info is : id=100 accessTime=2013-08-16 21:20:33 <<10>>: User info is : id=100 accessTime=2013-08-16 21:20:33 [100]从db中获取 <<11>>: User info is : id=100 accessTime=2013-08-16 21:20:38 <<12>>: User info is : id=100 accessTime=2013-08-16 21:20:38 <<13>>: User info is : id=100 accessTime=2013-08-16 21:20:38 <<14>>: User info is : id=100 accessTime=2013-08-16 21:20:38 <<15>>: User info is : id=100 accessTime=2013-08-16 21:20:38 <<16>>: User info is : id=100 accessTime=2013-08-16 21:20:38 <<17>>: User info is : id=100 accessTime=2013-08-16 21:20:38 <<18>>: User info is : id=100 accessTime=2013-08-16 21:20:38 <<19>>: User info is : id=100 accessTime=2013-08-16 21:20:38
小结及其引申
缓存是在提升系统响应时常用的一种技术,在系统缓存上通常采用的是有页面缓存、处理缓存和数据缓存这三种具体的类别,应该说这三种缓存在实现上还是稍有不同,尽管底层的缓存实现是一样的。
页面缓存
页面缓存是指对页面中的内容片断进行缓存的方案。比如页面中有一个部分是显示栏目中的内容的,那么就可以缓存这个部分,在进行第二次请求的时候就直接从缓存中取出这部分的内容(其实就是这部分的html了),这种情况下,缓存的作用其实非常明显,在典型的action+service+dao这样的结构中,在采用页面缓存后就意味着不需要经过action、service、dao这些层次的处理了,而是直接就返回了,对于系统响应速度的提升来说是非常明显的。
页面缓存通常采用oscache来进行实现,oscache提供了一个jsp tag,可通过这个tag来包含需要缓存的内容部分,当然,缓存的这个内容部分需要有对服务器的请求或逻辑计算等的,可想而知,去缓存一段静态html是没有意义的。
其次需要定义缓存的这段内容的key,例如我们要去缓存页面中某个栏目的某页的内容,对于这段内容而言唯一的key就是栏目ID以及当前页数,这样就组成了这段缓存的key了,其实这个部分看起来好像是很简单,但有些时候会很麻烦,要仔细的想清楚这段内容的唯一的标识的key到底是什么,^_^,通常的做法其实可以从action中需要获取的参数或service接口的参数来决定....
页面缓存中还需要做的一个步骤就是通知缓存需要更新,页面缓存和其他缓存稍有不同,需要告诉它,这个时候不能再使用缓存中的内容了,需要从后台再重新获取来生成新的缓存内容,这个其实很简单,因为很难在后台发生变化的时候自己来更新缓存的内容,只能是去通知它,然后让它再次发起请求来生成新的内容放入缓存中。
页面的缓存的使用对于系统的响应速度确实会有很大的提升,在实现页面缓存时最麻烦的主要是缓存的key的定义以及缓存更新的通知,缓存key的定义这个自然框架是没法解决的,不过缓存更新的通知其实在框架中可以考虑一种通知模型的,^_^,就像事件通知那样........在实际的项目中,可以自己去实现一个这样的通知模型或者就是简单的采用单例方式来标识某个key是否需要更新。
页面缓存在实际的项目中使用非常的多。
处理缓存
处理缓存是指对于action、service、dao或者系统层次中的某方法进行缓存,说直接点,就是对某个类的某个方法的结果做缓存,这样在下次进行完全相同的请求的时候就可以直接取缓存了,这种响应速度的提升也是非常明显的。
处理缓存在现在的情况下其实采用任务的缓存工具包都可以实现,如oscache、ehcache、jbosscache等,但目前还没有处理缓存框架的出现,这个和处理缓存是否应该存在的意义也是有关系的,处理缓存框架要做到的其实就像拦截一样的方式,和oscache tag类似。
同样,处理缓存的麻烦也在于怎么样去定义这个key,很多情况下可以根据方法的输入作为key,方法的输出作为key的值,但也会有其他一些复杂的情况,这个时候定义key就会变得复杂些了。
处理缓存同样有通知更新缓存的情况,和页面缓存基本是一样的。
应该说,处理缓存和页面缓存非常的相似,从实现上来说基本是完全一致的,在使用上来讲处理缓存使用的好像不多。
数据缓存
数据缓存估计大家都很熟悉,就是对系统的数据进行缓存的方式,典型的就是Hibernate的一级、二级数据缓存。
数据缓存在实现上如果是用hibernate的话更多的是直接使用hibernate的一级、二级以及查询缓存,如果自己要实现的话可以去参考hibernate的实现机制。
数据缓存的key在一级、二级缓存中采用的都是数据的标识键的值的方式,查询缓存采用的是查询参数、查询语句的方式。
数据缓存的更新则是hibernate在进行存储时直接更新缓存的内容,而对于查询缓存则是采用全部直接清除的方式,这样在下次进行查询时自然会重新去查询,^_^,大家可能会想,为什么页面缓存和处理缓存不采用这样的方式来实现缓存的更新,稍微想想就知道了,在后台发生改变的时候其实是不知道需要移除哪些key的,所以hibernate为了避免这个麻烦,采用的就是当数据一旦发生改变的时候就清除全部的查询缓存,而不是只去清除相关的缓存,其实这里可以采用一种订阅式的模型,呵呵,当然,也增加了框架的复杂度。
数据缓存使用的应该是最多的,效果也是很明显的。
以上三种缓存是目前缓存实现时通常碰到的三种状况,里面按使用的多少来排序应该是:数据缓存、页面缓存和处理缓存;实现的难度上从难到易的顺序应该是:处理缓存、页面缓存、数据缓存;对于系统响应速度提升的效果来说从最好到好的顺序应该是:页面缓存、处理缓存、数据缓存。