自己在开发JavaEE的项目时,采用了基于Spring MVC + MyBatis +Sitemesh +AngularJS + semantic-ui的组合,使用maven作为项目管理、SVN作为代码版本控制工具。
前台通过ajax从后台获取数据,再在前台进行DOM渲染,于是,数据加载的时候,页面会有一定程度的"空白"现象。
为了解决这个问题,最好的办法的是把动态页面静态化,页面只进行一次渲染,但这种方式,略显麻烦,于是自己采取了片段化缓存和数据缓存的方式,加快
页面渲染和数据加载。
1.配置
页面片段化缓存
为了统一页面风格,我使用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>
2.源码
个人从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过滤后,添加标志位,避免重复缓存。