問(wèn)題
為了繼承公共的父項(xiàng)目html靜態(tài)資源,致讥,我們希望在子項(xiàng)目相同路徑下有文件時(shí)仅仆,覆蓋父項(xiàng)目資源文件,沒(méi)有的時(shí)候直接獲取父項(xiàng)目垢袱。
但是freemark在尋找視圖的時(shí)候墓拜,發(fā)現(xiàn)無(wú)法找到父類(lèi)靜態(tài)視圖資源。
解決
在配置文件中增加以下配置
# Whether to prefer file system access for template loading.
#File system access enables hot detection of template changes.
spring.freemarker.prefer-file-system-access=false
根據(jù)官方解釋為:
是否優(yōu)先從從文件系統(tǒng)中獲取模板请契,以支持熱加載咳榜,默認(rèn)為true。
從官方文檔描述中爽锥,得出以下結(jié)論:
1涌韩、如果設(shè)置true,會(huì)優(yōu)先使用文件路徑獲取【咦氯夷,這不是廢話嗎臣樱?】。
2、如果設(shè)置為false雇毫,不支持熱加載數(shù)據(jù)玄捕。
但是經(jīng)過(guò)實(shí)踐發(fā)現(xiàn),以上結(jié)論都是錯(cuò)誤的E锓拧C墩场!
我們要繼承父項(xiàng)目席吴,讀取父模板內(nèi)容赌结,需要設(shè)置prefer-file-system-access=false,否則會(huì)報(bào)404無(wú)法找到視圖孝冒。
并且設(shè)置為false后柬姚,數(shù)據(jù)熱加載測(cè)試依然可以正常運(yùn)行。
那是什么原因?qū)е耡pi文檔和實(shí)際操作過(guò)程中截然不同的答案呢庄涡?我們研究下源碼
原因
從入口開(kāi)始追蹤
@ConfigurationProperties(prefix = "spring.freemarker")
public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {
public boolean isPreferFileSystemAccess() {
return this.preferFileSystemAccess;
}
public void setPreferFileSystemAccess(boolean preferFileSystemAccess) {
this.preferFileSystemAccess = preferFileSystemAccess;
}
}
看下圖量承,我們可以看到freferFileSystemAccess字段,只被方法isPreferFileSystemAccess調(diào)用穴店。
跟蹤方法到了核心判定方法:
public class FreeMarkerConfigurationFactory {
protected TemplateLoader getTemplateLoaderForPath(String templateLoaderPath) {
if (isPreferFileSystemAccess()) {
// Try to load via the file system, fall back to SpringTemplateLoader
// (for hot detection of template changes, if possible).
try {
Resource path = getResourceLoader().getResource(templateLoaderPath);
File file = path.getFile(); // will fail if not resolvable in the file system
if (logger.isDebugEnabled()) {
logger.debug(
"Template loader path [" + path + "] resolved to file path [" + file.getAbsolutePath() + "]");
}
return new FileTemplateLoader(file);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot resolve template loader path [" + templateLoaderPath +
"] to [java.io.File]: using SpringTemplateLoader as fallback", ex);
}
return new SpringTemplateLoader(getResourceLoader(), templateLoaderPath);
}
}
else {
// Always load via SpringTemplateLoader (without hot detection of template changes).
logger.debug("File system access not preferred: using SpringTemplateLoader");
return new SpringTemplateLoader(getResourceLoader(), templateLoaderPath);
}
}
}
我們看到撕捍,這里進(jìn)行邏輯區(qū)分,看起來(lái)true沒(méi)問(wèn)題泣洞。只能跟蹤下代碼
true:優(yōu)先從資源文件中獲取忧风,如果異常,走fasle邏輯
false:new SpringTemplateLoader(getResourceLoader(), templateLoaderPath);
跟蹤代碼發(fā)現(xiàn):
這個(gè)代碼僅在初始化執(zhí)行球凰,判定文件目錄是否存在狮腿,并非文件是否存在。
所以會(huì)導(dǎo)致后續(xù)使用時(shí)呕诉,直接使用FileTemplateLoader,導(dǎo)致無(wú)法正常加載缘厢。我們?cè)賮?lái)驗(yàn)證下:
/*---------FreeMarkerConfigurationFactory begin---------*/
List<TemplateLoader> templateLoaders = new ArrayList<>(this.templateLoaders);
if (this.templateLoaderPaths != null) {
for (String path : this.templateLoaderPaths) {
templateLoaders.add(getTemplateLoaderForPath(path));
}
}
...
//對(duì)象轉(zhuǎn)數(shù)組,創(chuàng)建TemplateLoader 對(duì)象
TemplateLoader loader = getAggregateTemplateLoader(templateLoaders);
//config設(shè)置loader對(duì)象
config.setTemplateLoader(loader);
/*---------FreeMarkerConfigurationFactory end--------*/
講loader放入對(duì)象
public class TemplateCache {
private final TemplateLoader templateLoader;
public TemplateCache(TemplateLoader templateLoader, ...) {
this.templateLoader = templateLoader;
}
}
我們可以看到templateLoader最終使用場(chǎng)景
太多了甩挫,不過(guò)沒(méi)關(guān)系贴硫,研究過(guò)freemarker渲染邏輯知道。獲取視圖核心源碼:
final MaybeMissingTemplate maybeTemp = cache.getTemplate(name, locale, customLookupCondition, encoding, parseAsFTL);
//繼續(xù)跟進(jìn)
Template template = getTemplateInternal(name, locale, customLookupCondition, encoding, parseAsFTL);
可以看到我們440行伊者,既是讀取loader英遭。
lastModified = lastModified == Long.MIN_VALUE ? templateLoader.getLastModified(source) : lastModified;
Template template = loadTemplate(
templateLoader, source,
name, newLookupResult.getTemplateSourceName(), locale, customLookupCondition,
encoding, parseAsFTL);
cachedTemplate.templateOrException = template;
cachedTemplate.lastModified = lastModified;
storeCached(tk, cachedTemplate);
但是,通過(guò)斷點(diǎn)返現(xiàn)亦渗,沒(méi)有運(yùn)行到440行挖诸,被前面420行代碼截胡了
newLookupResult = lookupTemplate(name, locale, customLookupCondition);
if (!newLookupResult.isPositive()) {
storeNegativeLookup(tk, cachedTemplate, null);
return null;
}
最終結(jié)果策略模式一陣?yán)@,到了代碼代碼791即央碟,上面以后一行
//策略模式?
@Override
public TemplateLookupResult lookup(TemplateLookupContext ctx) throws IOException {
return ctx.lookupWithLocalizedThenAcquisitionStrategy(ctx.getTemplateName(), ctx.getTemplateLocale());
}
private Object findTemplateSource(String path) throws IOException {
final Object result = templateLoader.findTemplateSource(path);
if (LOG.isDebugEnabled()) {
LOG.debug("TemplateLoader.findTemplateSource(" + StringUtil.jQuote(path) + "): "
+ (result == null ? "Not found" : "Found"));
}
return modifyForConfIcI(result);
}
ok,至此,我們可以確認(rèn)亿虽,最后的加載策略菱涤,就是通過(guò)初始化的loader進(jìn)行加載的.
我們來(lái)看下,兩種classLoader最后的區(qū)別:
可以看到洛勉,如果設(shè)置為false粘秆,我們使用的是SpringTemplateLoader.
SpringTemplateLoader如何實(shí)現(xiàn)讀取父目錄的代碼的呢?
2個(gè)問(wèn)題
為什么要用策略模式
中間420都截胡了收毫,后面440代碼還有什么用呢攻走。
繼續(xù)未完的游戲
templateLoader.findTemplateSource(path);
如何可以實(shí)現(xiàn),有文件的時(shí)候優(yōu)先讀取文件此再,沒(méi)有文件的時(shí)候讀取父項(xiàng)目中的內(nèi)容昔搂。
for (TemplateLoader templateLoader : templateLoaders) {
if (lastTemplateLoader != templateLoader) {
Object source = templateLoader.findTemplateSource(name);
if (source != null) {
if (sticky) {
lastTemplateLoaderForName.put(name, templateLoader);
}
return new MultiSource(source, templateLoader);
}
}
}
擁有兩個(gè)對(duì)象,file對(duì)象在上面输拇,classLoader在下面摘符,故會(huì)優(yōu)先讀取file中的內(nèi)容。