开发springmvc+freemarker项目时经常要调试页面模版的元素,为了使修改立即生效,就得关闭缓存,否则视图渲染一直走得缓存。关闭缓存就要注意了,如果使用FreeMarkerViewResolver视图解析器(freemarker模版的解析器),则需要关闭两处缓存。
第一级缓存,FreeMarkerViewResolver的视图缓存(viewAccessCache)。这级缓存是springmvc的视图缓存,mvc会将之前解析得到的view对象缓存到一个map里面。当map的size达到一定数值时(默认是1024),则删除第一个元素。想要关闭这级缓存只需要将缓存的限制值(cacheLimit)为0即可。
<!-- Spring MVC页面层FreeMarker的处理类 -->
<bean id="freeMarkerViewResolver"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.freemarker.FreeMarkerView"/>
<property name="cacheLimit" value="0" />
</bean>
springmvc用了巧妙的方式来保证map的size不超过cacheLimit。mvc维护了两个map,一个是viewAccessCache,另一个是viewCreationCache。viewAccessCache用于访问,viewCreationCache用于创建。mvc怎么保证map的size不超过cacheLimit呢?它在创建viewCreationCache时重载了removeEldestEntry方法,viewCreationCache添加元素后当map的size > cacheLimit时就会删除viewAccessCache里面对应key的view对象,并返回true从而使viewCreationCache也删除自己保存的对象。
public abstract class AbstractCachingViewResolver
extends WebApplicationObjectSupport implements ViewResolver {
......
/** Fast access cache for Views, returning already cached instances without a global lock */
private final Map<Object, View> viewAccessCache =
new ConcurrentHashMap<Object, View>(DEFAULT_CACHE_LIMIT);
/** Map from view key to View instance, synchronized for View creation */
@SuppressWarnings("serial")
private final Map<Object, View> viewCreationCache =
new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
if (size() > getCacheLimit()) {
viewAccessCache.remove(eldest.getKey());
return true;
}
else {
return false;
}
}
};
......
}
viewReslver解析view的时候判断有没有开启缓存(cacheLimit > 0时isCache就会返回true)。如果有就去获取viewAccessCache是否有缓存,有则返回。如果viewAccessCache没有就去创建view对象,创建成功后把view对象put到viewAccessCache和viewCreationCache。注意viewCreationCache重载了方法removeEldestEntry,put对象进去就会触发该方法删除viewAccessCache(当size > cacheLimit时),同时返回true从而使viewCreationCache也删除自己维护的对象。
public abstract class AbstractCachingViewResolver
extends WebApplicationObjectSupport implements ViewResolver {
......
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
//笔者注:cacheLimit > 0 isCache返回true
if (!isCache()) {
return createView(viewName, locale);
} else {
Object cacheKey = getCacheKey(viewName, locale);
//笔者注:判断viewAccessCache里面有没有,没有则去创建流程
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
view = createView(viewName, locale);
......
if (view != null) {
this.viewAccessCache.put(cacheKey, view);
//笔者注:put就会触发上面重载的方法(removeEldestEntry)
this.viewCreationCache.put(cacheKey, view);
......
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
......
}
第二级缓存,模版缓存(TemplateCache)。绕过FreeMarkerViewResolverviewResolver的缓存后,会来到freemarker加载模版(ftl文件)的逻辑,这个过程中同样也用到了缓存。这级缓存是用时间控制的,缓存中模版加载的时间超过delay则会重新加载模版文件。想要关闭这级缓存需要将delay(配置项为template_update_delay)时间设置为0,这样相当于设置模版缓存的有效期是0s。
<bean id="freemarkerConfiguration"
class="org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean">
<property name="freemarkerSettings">
<props>
<!-- 配置缓存时间 -->
<prop key="template_update_delay">0</prop>
</props>
</property>
</bean>
总结一下,调试mvc要想页面实时生效,需要关闭两个缓存:
- FreeMarkerViewResolver的cacheLimit设置为0
- freemarkerSettings的template_update_delay也要设置为0(单位s)