`
zddava
  • 浏览: 243026 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

oscache源代码阅读(四) -- JSP/Servlet缓存CacheFilter

阅读更多
oscache对于jsp/servlet的缓存是使用Filter来实现的,对应的类是com.opensymphony.oscache.web.filter.CacheFilter,既然是Filter那么要看的自然主要有三个方法:init、doFilter和destroy,这里#destroy()并没有具体实现,只关注前两个即可,首先看一下#init()方法,

	public void init(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);
			} else if ("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);
			} else if ("yes".equalsIgnoreCase(fragmentParam)) {
				setFragment(FRAGMENT_YES);
			} else if ("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;
			} else if ("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;
			} else if ("on".equalsIgnoreCase(lastModifiedParam)) {
				lastModified = LAST_MODIFIED_ON;
			} else if ("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);
			} else if ("on".equalsIgnoreCase(expiresParam)) {
				setExpires(EXPIRES_ON);
			} else if ("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);
			} else if (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(new ExpiresRefreshPolicy(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类实例的创建。

	public synchronized static ServletCacheAdministrator 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 = new ServletCacheAdministrator(context, p);
			Map admins = (Map) context.getAttribute(CACHE_ADMINISTRATORS_KEY);
			if (admins == null) {
				admins = new HashMap();
			}
			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;
		}

		return admin;
	}

	public Cache getAppScopeCache(ServletContext context) {
		Cache cache;
		// 首先尝试在ServletContext中查询App级的缓存
		Object obj = context.getAttribute(getCacheKey());

		if ((obj == null) || !(obj instanceof Cache)) {
			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;
		}

		return cache;
	}

	private ServletCache createCache(int scope, String sessionId) {
		// 创建ServletCache
		ServletCache newCache = new ServletCache(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 (int i = 0; i < listeners.length; i++) {
				if (listeners[i] instanceof ScopeEventListener) {
					newCache.addCacheEventListener(listeners[i]);
				}
			}
		}

		return newCache;
	}


看过#init()方法后就轮到#doFilter()方法了,这是真正每次对请求进行缓存的地方:

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws ServletException, 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参数
		boolean fragmentRequest = 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);
			}

			boolean acceptsGZip = false;
			// 这里是对客户端缓存的处理,判断下请求中是否包含If-Modified-Since头信息
			if ((!fragmentRequest) && (lastModified != LAST_MODIFIED_OFF)) {
				long clientLastModified = 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) {
			// 如果缓存中还没有想要的数据或者缓存需要刷新
			boolean updateSucceeded = false;

			try {
				if (log.isInfoEnabled()) {
					log.info("OSCache: New cache entry, cache stale or cache scope flushed for " + key);
				}

				// 这里用CacheHttpServletResponseWrapper来代替原来的response对象继续请求处理
				CacheHttpServletResponseWrapper cacheResponse = new CacheHttpServletResponseWrapper(
						(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的相关源代码。
3
4
分享到:
评论
2 楼 PetrusXu 2011-10-30  
咋不更新了呢?
1 楼 JustDone 2010-12-20  
期待后续大作

相关推荐

    JavaEE源代码 oscache-2.1

    JavaEE源代码 oscache-2.1JavaEE源代码 oscache-2.1JavaEE源代码 oscache-2.1JavaEE源代码 oscache-2.1JavaEE源代码 oscache-2.1JavaEE源代码 oscache-2.1JavaEE源代码 oscache-2.1JavaEE源代码 oscache-2.1JavaEE源...

    ssm整合文档。看着文档自己操作,很简单

    为了提高系统的性能,可以使用OSCache对频繁访问的数据进行缓存。具体配置如下: - **配置OSCache过滤器** ```xml &lt;filter-name&gt;oscache&lt;/filter-name&gt; &lt;filter-class&gt;...

    Oscache框架的搭建步骤

    Oscache框架作为一种高效、灵活的缓存解决方案,在Java Web应用,尤其是JSP环境中,提供了强大的缓存管理功能。本文将深入探讨Oscache框架的搭建步骤及其实现原理,帮助开发者掌握这一技术,从而显著提高Web系统的...

    基于OSCache的页面缓存

    【基于OSCache的页面缓存】是Web应用中一种有效的性能优化策略,它涉及到缓存技术、分布式系统以及系统开发等多个领域。OSCache是开源的Java缓存框架,能够帮助开发者实现高效的页面和数据缓存,从而降低数据库的...

    oscache详细配置文档

    OSCache 是一个强大的开源缓存解决方案,主要用于提升 Java Web 应用程序的性能。它能够缓存页面内容,减轻数据库压力,并减少服务器的资源消耗。本文将详细介绍 OSCache 的配置和使用方法。 **一、缓存整个页面** ...

    oscache-JSP缓存

    - 编写Java代码:在Servlet或Controller中,使用osCache API进行缓存操作。 **5. 示例应用** 以下是一个简单的osCache在JSP页面中的应用示例: ```jsp &lt;%@ taglib uri="http://...

    oscache说明

    通常,这两个文件会放在项目的源代码目录(如 `src`),在构建过程中会被自动复制到 `WEB-INF/classes`。 3. 根据你的应用需求,配置 `oscache.properties` 文件以设置缓存参数。 **使用方法** OSCache 提供了多种...

    oscache-2.1.jar

    oscache-2.1.jar oscache-2.1.jar

    缓存说明.doc

    oscache的部署步骤包括引入相关依赖库,如log4j、oscache、commons-logging等,并配置web.xml,启用`CacheFilter`来拦截JSP页面请求,如下所示: ```xml &lt;taglib-uri&gt;oscache&lt;/taglib-uri&gt; &lt;taglib-location&gt;/...

    OSCache学习心得

    在本篇文章中,我们将详细介绍OSCache框架的基本原理、配置方法以及如何在JSP页面中实现缓存功能,同时也会探讨OSCache与Struts2框架结合使用的具体实践。 #### 一、OSCache简介 OSCache是OpenSymphony组织开发的...

    oscache缓存配置

    在本例中,我们已经有了oscache-2.4.1.jar,这是一个包含osCache核心库的文件。在Java项目中,通常将其添加到项目的类路径(classpath)中,以便程序能够找到并使用osCache的相关类。 接着,我们需要配置osCache的...

    oscache-2.4.1-full

    OSCache是OpenSymphony开发的一款高效、...总之,OSCache-2.4.1是一个强大的缓存解决方案,旨在提高JSP应用的性能,减轻数据库压力,通过简单易用的JSP标记库和完善的配置选项,使得开发者能快速集成并优化缓存策略。

    oscache,缓存机制的使用

    - `oscache.tld`定义了oscache提供的标签库结构,便于在JSP页面中调用缓存相关的标签。 #### 3. 修改web.xml 在项目的`web.xml`文件中添加对oscache标签库的支持,具体代码如下: ```xml &lt;jsp-config&gt; &lt;taglib...

    oscache-2.4.1-full.rar

    OSCache标记库由OpenSymphony设计,它是一种开创性的缓存方案,它提供了在现有JSP页面之内实现内存缓存的功能。OSCache是个一个被广泛采用的高性能的J2EE缓存框架,OSCache还能应用于任何Java应用程序的普通的缓存...

    oscache缓存使用总结.doc

    OSCache的使用包括在JSP或Servlet中使用OSCache标签,例如`&lt;oscache:cache&gt;`,或者通过API进行编程式缓存操作。可以通过设置key和expiration时间来缓存特定内容,当数据变化时,可以自动更新缓存。 在实际应用中,...

    OsCache缓存框架使用示例

    OsCache是Java应用程序中常用的缓存框架,它能够有效地提高应用程序的性能,通过将经常访问的数据存储在内存中,减少对数据库或其他数据源的访问,从而降低系统负载。本示例将通过一个天气预报Web服务的场景,详细...

    oscache缓存技术

    压缩包中的代码示例可能包括了如何创建osCache实例、配置缓存、添加和检索缓存对象的步骤。通过学习这些示例,开发者可以更好地理解osCache的工作原理,并将其应用到实际项目中。 总结来说,osCache是一个强大的...

Global site tag (gtag.js) - Google Analytics