自己在开发JavaEE的项目时,采用了基于Spring MVC + MyBatis +Sitemesh +AngularJS + semantic-ui的组合,使用maven作为项目管理、SVN作为代码版本控制工具。
前台通过ajax从后台获取数据,再在前台进行DOM渲染,于是,数据加载的时候,页面会有一定程度的"空白"现象。
为了解决这个问题,最好的办法的是把动态页面静态化,页面只进行一次渲染,但这种方式,略显麻烦,于是自己采取了片段化缓存和数据缓存的方式,加快
页面渲染和数据加载。
为了统一页面风格,我使用Sitemesh来做页面布局。于是,页首的导航栏、菜单栏、页面底部的版权信息,可以直接缓存掉,比如下图(-1表示永久缓存)。
打开页面的速度确实提升很快。
对于页面、数据接口进行全局缓存,在web.xml配置如下:
<!-- osCacheFilter --> <filter> <filter-name>osCacheFilter</filter-name> <filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class> <init-param> <param-name>time</param-name> <param-value>7200</param-value><!-- 2h --> </init-param> <init-param> <param-name>scope</param-name> <param-value>application</param-value><!-- default --> </init-param> </filter> <filter-mapping> <filter-name>osCacheFilter</filter-name> <url-pattern>/</url-pattern><!-- home page --> </filter-mapping> <filter-mapping> <filter-name>osCacheFilter</filter-name> <url-pattern>/group/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>osCacheFilter</filter-name> <url-pattern>/article/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>osCacheFilter</filter-name> <url-pattern>/case/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>osCacheFilter</filter-name> <url-pattern>/disease/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>osCacheFilter</filter-name> <url-pattern>/doctor/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>osCacheFilter</filter-name> <url-pattern>/drug/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>osCacheFilter</filter-name> <url-pattern>/hospital/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>osCacheFilter</filter-name> <url-pattern>/page/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>osCacheFilter</filter-name> <url-pattern>/u/*</url-pattern> </filter-mapping>
个人从github上得到相关源码,地址:https://github.com/cdemoustier/opensymphony-oscache-backup
web.xml这么配置参数,是如何被OSCache得到呢?
先看com.opensymphony.oscache.web.filter.CacheFilter类,它的init方法如下:
/** * Initialize the filter. This retrieves a {@link ServletCacheAdministrator} * instance and configures the filter based on any initialization * parameters. * <p> * The supported initialization parameters are: * <ul> * * <li><b>oscache-properties-file</b> - the properties file that contains * the OSCache configuration options to be used by the Cache that this * Filter should use.</li> * * @param filterConfig * The filter configuration */ public void init(FilterConfig filterConfig) { // Get whatever settings we want... config = filterConfig; log.info("OSCache: Initializing CacheFilter with filter name " + config.getFilterName()); // setting the request filter to avoid reentrance with the same filter requestFiltered = REQUEST_FILTERED + config.getFilterName(); log.info("Request filter attribute is " + requestFiltered); // filter Properties file Properties props = null; try { 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."); } admin = ServletCacheAdministrator.getInstance( config.getServletContext(), props); // filter parameter time 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()); } } // filter parameter scope 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'."); } } // filter parameter cron setCron(config.getInitParameter("cron")); .... // filter parameter scope String disableCacheOnMethodsParam = config .getInitParameter("disableCacheOnMethods"); if (StringUtil.hasLength(disableCacheOnMethodsParam)) { disableCacheOnMethods = StringUtil.split( disableCacheOnMethodsParam, ','); // log.error("OSCache: Wrong value '" + disableCacheOnMethodsParam + // "' for init parameter 'disableCacheOnMethods', defaulting to 'null'."); } }
通过FilterConfig filterConfig来获取参数值,完成初始化。
那Cache是如何对request进行过滤呢?
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { if (log.isInfoEnabled()) { log.info("OSCache: filter in scope " + cacheScope); } // avoid reentrance (CACHE-128) and check if request is cacheable 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 boolean fragmentRequest = isFragment(httpRequest); // avoid useless session creation for application scope pages // (CACHE-129) Cache cache; if (cacheScope == PageContext.SESSION_SCOPE) { cache = admin.getSessionScopeCache(httpRequest.getSession(true)); } else { cache = admin.getAppScopeCache(config.getServletContext()); } // generate the cache entry key String key = cacheKeyProvider.createCacheKey(httpRequest, admin, cache); try { ResponseContent respContent = (ResponseContent) cache.getFromCache( key, time, cron); if (log.isInfoEnabled()) { log.info("OSCache: Using cached entry for " + key); } boolean acceptsGZip = false; if ((!fragmentRequest) && (lastModified != LAST_MODIFIED_OFF)) { long clientLastModified = httpRequest .getDateHeader(HEADER_IF_MODIFIED_SINCE); // will return // -1 if no // header... // only reply with SC_NOT_MODIFIED // if the client has already the newest page and the response // isn't a fragment in a page if ((clientLastModified != -1) && (clientLastModified >= respContent.getLastModified())) { ((HttpServletResponse) response) .setStatus(HttpServletResponse.SC_NOT_MODIFIED); return; } acceptsGZip = respContent.isContentGZiped() && acceptsGZipEncoding(httpRequest); } respContent.writeTo(response, fragmentRequest, acceptsGZip); // acceptsGZip is used for performance reasons above; use the // following line for CACHE-49 // respContent.writeTo(response, fragmentRequest, // acceptsGZipEncoding(httpRequest)); } 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 cacheResponse = new CacheHttpServletResponseWrapper( (HttpServletResponse) response, fragmentRequest, time * 1000L, lastModified, expires, cacheControlMaxAge, etag); chain.doFilter(request, cacheResponse); cacheResponse.flushBuffer(); // Only cache if the response is cacheable if (isCacheableInternal(cacheResponse)) { // get the cache groups of the content 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); } } } }
对request过滤后,添加标志位,避免重复缓存。
原文地址:http://blog.csdn.net/u010786672/article/details/45508273