publicvoidinit(FilterConfig filterConfig){
config=filterConfig;
log.info("OSCache: Initializing CacheFilter with filter name"+config.getFilterName());
//此变量用于防治请求被重复的缓存
requestFiltered=REQUEST_FILTERED+config.getFilterName();
log.info("Request filter attribute is"+requestFiltered);
//读取配置文件
Properties props=null;
try{
//首先按照Filter参数指定的地方读取配置文件,否则读取默认位置的
String propertiesfile=config.getInitParameter("oscache-properties-file");
if(propertiesfile!=null&&propertiesfile.length()>0){
props=Config.loadProperties(propertiesfile,
"CacheFilter with filter name '"+config.getFilterName()+"'");
}
}catch(Exception e){
log.info("OSCache: Init parameter 'oscache-properties-file' not set, using default.");
}
//实例化ServletCacheAdministrator,ServletCacheAdministrator只有一个实例,这里需要关注一下
admin=ServletCacheAdministrator.getInstance(config.getServletContext(), props);
//缓存超时时间
String timeParam=config.getInitParameter("time");
if(timeParam!=null){
try{
setTime(Integer.parseInt(timeParam));
}catch(NumberFormatException nfe){
log.error("OSCache: Unexpected value for the init parameter 'time', defaulting to one hour. Message="
+nfe.getMessage());
}
}
//缓存范围
String scopeParam=config.getInitParameter("scope");
if(scopeParam!=null){
if("session".equalsIgnoreCase(scopeParam)){
setCacheScope(PageContext.SESSION_SCOPE);
}elseif("application".equalsIgnoreCase(scopeParam)){
setCacheScope(PageContext.APPLICATION_SCOPE);
}else{
log.error("OSCache: Wrong value '"+scopeParam
+"' for init parameter 'scope', defaulting to 'application'.");
}
}
//利用"计划任务"表达式来处理超时时间
setCron(config.getInitParameter("cron"));
//是否处理include
String fragmentParam=config.getInitParameter("fragment");
if(fragmentParam!=null){
if("no".equalsIgnoreCase(fragmentParam)){
setFragment(FRAGMENT_NO);
}elseif("yes".equalsIgnoreCase(fragmentParam)){
setFragment(FRAGMENT_YES);
}elseif("auto".equalsIgnoreCase(fragmentParam)){
setFragment(FRAGMENT_AUTODETECT);
}else{
log.error("OSCache: Wrong value '"+fragmentParam
+"' for init parameter 'fragment', defaulting to 'auto detect'.");
}
}
//是否处理URL里包括session id的请求
String nocacheParam=config.getInitParameter("nocache");
if(nocacheParam!=null){
if("off".equalsIgnoreCase(nocacheParam)){
nocache=NOCACHE_OFF;
}elseif("sessionIdInURL".equalsIgnoreCase(nocacheParam)){
nocache=NOCACHE_SESSION_ID_IN_URL;
}else{
log.error("OSCache: Wrong value '"+nocacheParam
+"' for init parameter 'nocache', defaulting to 'off'.");
}
}
//是否处理写入到response中的header属性Last-Modified
String lastModifiedParam=config.getInitParameter("lastModified");
if(lastModifiedParam!=null){
if("off".equalsIgnoreCase(lastModifiedParam)){
lastModified=LAST_MODIFIED_OFF;
}elseif("on".equalsIgnoreCase(lastModifiedParam)){
lastModified=LAST_MODIFIED_ON;
}elseif("initial".equalsIgnoreCase(lastModifiedParam)){
lastModified=LAST_MODIFIED_INITIAL;
}else{
log.error("OSCache: Wrong value '"+lastModifiedParam
+"' for init parameter 'lastModified', defaulting to 'initial'.");
}
}
//是否处理写入到response中的header属性Expires
String expiresParam=config.getInitParameter("expires");
if(expiresParam!=null){
if("off".equalsIgnoreCase(expiresParam)){
setExpires(EXPIRES_OFF);
}elseif("on".equalsIgnoreCase(expiresParam)){
setExpires(EXPIRES_ON);
}elseif("time".equalsIgnoreCase(expiresParam)){
setExpires(EXPIRES_TIME);
}else{
log.error("OSCache: Wrong value '"+expiresParam
+"' for init parameter 'expires', defaulting to 'on'.");
}
}
//是否处理写入到response中的header属性Cache-Control
String cacheControlMaxAgeParam=config.getInitParameter("max-age");
if(cacheControlMaxAgeParam!=null){
if(cacheControlMaxAgeParam.equalsIgnoreCase("no init")){
setCacheControlMaxAge(MAX_AGE_NO_INIT);
}elseif(cacheControlMaxAgeParam.equalsIgnoreCase("time")){
setCacheControlMaxAge(MAX_AGE_TIME);
}else{
try{
setCacheControlMaxAge(Long.parseLong(cacheControlMaxAgeParam));
}catch(NumberFormatException nfe){
log.error("OSCache: Unexpected value for the init parameter 'max-age', defaulting to '60'. Message="
+nfe.getMessage());
}
}
}
//ICacheKeyProvider的实例,用于创建缓存的key
ICacheKeyProvider cacheKeyProviderParam=(ICacheKeyProvider) instantiateFromInitParam(
"ICacheKeyProvider", ICacheKeyProvider.class,this.getClass().getName());
if(cacheKeyProviderParam!=null){
setCacheKeyProvider(cacheKeyProviderParam);
}
//ICacheGroupsProvider的实例,用于创建缓存的group名字
ICacheGroupsProvider cacheGroupsProviderParam=(ICacheGroupsProvider) instantiateFromInitParam(
"ICacheGroupsProvider", ICacheGroupsProvider.class,this.getClass().getName());
if(cacheGroupsProviderParam!=null){
setCacheGroupsProvider(cacheGroupsProviderParam);
}
//EntryRefreshPolicy的实例,用于指定缓存过期策略
EntryRefreshPolicy expiresRefreshPolicyParam=(EntryRefreshPolicy) instantiateFromInitParam(
"EntryRefreshPolicy", EntryRefreshPolicy.class, ExpiresRefreshPolicy.class.getName());
if(expiresRefreshPolicyParam!=null){
setExpiresRefreshPolicy(expiresRefreshPolicyParam);
}else{
setExpiresRefreshPolicy(newExpiresRefreshPolicy(time));
}
//指定哪些请求方式不去缓存,如GET,POST等
String disableCacheOnMethodsParam=config.getInitParameter("disableCacheOnMethods");
if(StringUtil.hasLength(disableCacheOnMethodsParam)){
disableCacheOnMethods=StringUtil.split(disableCacheOnMethodsParam,',');
}
}
这个方法主要是对Filter的init-param的载入还有缓存管理器类实例的创建(里面会包括一个Application Scope的Cache的创建还有oscache配置文件的读取)。其中的这句调用时需要关注一下的,admin = ServletCacheAdministrator.getInstance(config.getServletContext(), props),也就是ServletCacheAdministrator类实例的创建。
publicsynchronizedstaticServletCacheAdministrator getInstance(ServletContext context, Properties p){
//Cache在ServletContext中的属性名
String adminKey=null;
if(p!=null){
//这里是oscache配置文件中的cache.key属性
adminKey=p.getProperty(CACHE_KEY_KEY);
}
if(adminKey==null){
adminKey=DEFAULT_CACHE_KEY;
}
//ServletCacheAdministrator在ServletContext中的键值要加上"_admin"这个后缀
adminKey+=CACHE_ADMINISTRATOR_KEY_SUFFIX;
//先尝试在ServletContext中找Cache,当然,第一次初始化时是不会找到的
ServletCacheAdministrator admin=(ServletCacheAdministrator) context.getAttribute(adminKey);
if(admin==null){
//实例化一个,并在ServletContext中设定好相关属性
admin=newServletCacheAdministrator(context, p);
Map admins=(Map) context.getAttribute(CACHE_ADMINISTRATORS_KEY);
if(admins==null){
admins=newHashMap();
}
admins.put(adminKey, admin);
context.setAttribute(CACHE_ADMINISTRATORS_KEY, admins);
context.setAttribute(adminKey, admin);
if(log.isInfoEnabled()){
log.info("Created new instance of ServletCacheAdministrator with key"+adminKey);
}
//创建Application级别的Cache
admin.getAppScopeCache(context);
}
if(admin.context==null){
admin.context=context;
}
returnadmin;
}
publicCache getAppScopeCache(ServletContext context){
Cache cache;
//首先尝试在ServletContext中查询App级的缓存
Object obj=context.getAttribute(getCacheKey());
if((obj==null)||!(objinstanceofCache)){
if(log.isInfoEnabled()){
log.info("Created new application-scoped cache at key:"+getCacheKey());
}
//创建一个缓存实例并放入ServletContext中
cache=createCache(PageContext.APPLICATION_SCOPE,null);
context.setAttribute(getCacheKey(), cache);
}else{
cache=(Cache) obj;
}
returncache;
}
privateServletCache createCache(intscope, String sessionId){
//创建ServletCache
ServletCache newCache=newServletCache(this, algorithmClass, cacheCapacity, scope);
//这里的2个参数是用于给持久化缓存构建缓存目录用的,这里要注意,Session级别的缓存可能会有问题
//因为config是全局唯一的,在并发访问的情况下如果多个session同时创建缓存会出现同步错误的
//所以session级别缓存是不是应该慎用磁盘缓存呢?
config.set(HASH_KEY_SCOPE,""+scope);
config.set(HASH_KEY_SESSION_ID, sessionId);
//初始化Cache监听器,包括磁盘缓存的处理类
newCache=(ServletCache) configureStandardListeners(newCache);
if(config.getProperty(CACHE_ENTRY_EVENT_LISTENERS_KEY)!=null){
//Add any event listeners that have been specified in the
//configuration
CacheEventListener[] listeners=getCacheEventListeners();
for(inti=0; i
if(listeners[i]instanceofScopeEventListener){
newCache.addCacheEventListener(listeners[i]);
}
}
}
returnnewCache;
}
看过#init()方法后就轮到#doFilter()方法了,这是真正每次对请求进行缓存的地方:
publicvoiddoFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throwsServletException, IOException{
if(log.isInfoEnabled()){
log.info("OSCache: filter in scope"+cacheScope);
}
//判断是否请求已经缓存或者是否能缓存
//#isFilteredBefore()判断request中是否包括了requestFiltered这个变量
//#isCacheableInternal()根据Filter的disableCacheOnMethods和nocache参数进行判断
if(isFilteredBefore(request)||!isCacheableInternal(request)){
chain.doFilter(request, response);
return;
}
//设置当前的请求已经缓存了
request.setAttribute(requestFiltered, Boolean.TRUE);
HttpServletRequest httpRequest=(HttpServletRequest) request;
//checks if the response will be a fragment of a page
//是否处理"include",如果是自动判断那么要判断请求中是否有javax.servlet.include.request_uri参数
booleanfragmentRequest=isFragment(httpRequest);
//根据不同的缓存范围来返回缓存实例
Cache cache;
if(cacheScope==PageContext.SESSION_SCOPE){
//#getSessionScopeCache()中返回的Cache是保存在Session中的
cache=admin.getSessionScopeCache(httpRequest.getSession(true));
}else{
//#getAppScopeCache()中返回的Cache是保存在ServletContext中的
cache=admin.getAppScopeCache(config.getServletContext());
}
//生成缓存的key,默认的cacheKeyProvider就是CacheFilter自己
//成生的key默认是请求路径+方法名(GET,POST)
String key=cacheKeyProvider.createCacheKey(httpRequest, admin, cache);
try{
//查找缓存,如果还没有加入缓存,会抛出NeedsRefreshException,进入异常处理路径
ResponseContent respContent=(ResponseContent) cache.getFromCache(key, time, cron);
if(log.isInfoEnabled()){
log.info("OSCache: Using cached entry for"+key);
}
booleanacceptsGZip=false;
//这里是对客户端缓存的处理,判断下请求中是否包含If-Modified-Since头信息
if((!fragmentRequest)&&(lastModified!=LAST_MODIFIED_OFF)){
longclientLastModified=httpRequest.getDateHeader(HEADER_IF_MODIFIED_SINCE);
//如果请求中的最后修改时间大于缓存的最后修改时间,那么就返回状态码304
if((clientLastModified!=-1)&&(clientLastModified>=respContent.getLastModified())){
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
//判断是否接受gzip压缩的响应,通过判断请求Header Accept-Encoding是否包含gzip
acceptsGZip=respContent.isContentGZiped()&&acceptsGZipEncoding(httpRequest);
}
//将缓存的内容写入响应
respContent.writeTo(response, fragmentRequest, acceptsGZip);
}catch(NeedsRefreshException nre){
//如果缓存中还没有想要的数据或者缓存需要刷新
booleanupdateSucceeded=false;
try{
if(log.isInfoEnabled()){
log.info("OSCache: New cache entry, cache stale or cache scope flushed for"+key);
}
//这里用CacheHttpServletResponseWrapper来代替原来的response对象继续请求处理
CacheHttpServletResponseWrapper cacheResponse=newCacheHttpServletResponseWrapper(
(HttpServletResponse) response, fragmentRequest, time*1000L, lastModified, expires,
cacheControlMaxAge);
//继续调用后边的Filter和Servlet
chain.doFilter(request, cacheResponse);
cacheResponse.flushBuffer();
//这里判断下响应码是否是200,只有"OK"才会缓存
if(isCacheableInternal(cacheResponse)){
//创建缓存的group,默认的cacheGroupsProvider也是自己
String[] groups=cacheGroupsProvider.createCacheGroups(httpRequest, admin, cache);
//Store as the cache content the result of the response
//将响应内容写入缓存
cache.putInCache(key, cacheResponse.getContent(), groups, expiresRefreshPolicy,null);
updateSucceeded=true;
if(log.isInfoEnabled()){
log.info("OSCache: New entry added to the cache with key"+key);
}
}
}finally{
if(!updateSucceeded){
//如果写入缓存失败,要取消更新,防止缓存出现错误的状态
cache.cancelUpdate(key);
}
}
}
}
这个方法的整个流程通过代码的注释其实是很好理解的,要注意的地方有两点:
首先,关注下异常路径里更新缓存的地方,这里用CacheHttpServletResponseWrapper来代替原来的Response对象来继续流程,通过对Response进行包装(Decorator)的方式来记录其要返回客户端的Header和页面内容(记录到ResponseContent类的属性中),其中页面内容的捕获是通过用SplitServletOutputStream类来代替原有的OutputStream来实现的,SplitServletOutputStream中包括了ResponseContent对象的一个ByteArrayOutputStream,每次写入页面响应的数据也都要记录在ByteArrayOutputStream中一份,而在调用Cache的putInCache()方法时有一个cacheResponse.getContent()方法,会返回ResponseContent类的属性,也就是真正要缓存的对象,并且将其ByteArrayOutputStream流中的数据"提交"到一个byte数组中保存下来,从而实现了响应数据的缓存。
其次,是将缓存的ResponseContent中的数据输出的过程,也就是这一句:respContent.writeTo(response, fragmentRequest, acceptsGZip);基本可以理解为上边缓存过程的逆过程,这里就不多说了,有兴趣的可以了解下CacheHttpServletResponseWrapper,ResponseContent,SplitServletOutputStream的相关源代码。
posted on 2010-12-20 08:42 臭美 阅读(3269) 评论(2) 编辑 收藏 所属分类: Cache