開發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)