模板加载器
模板加载器是加载基于抽象模板路径下,比如
"index.ftl"
或
"products/catalog.ftl"
的原生文本数据对象。
这由具体的模板加载器对象来确定它们取得请求数据时使用了什么样的数据来源
(文件夹中的文件,数据等等)。当调用 cfg.getTemplate
(这里的 cfg
就是 Configuration
实例)时,
FreeMarker询问模板加载器是否已经为 cfg
建立返回给定模板路径的文本,之后 FreeMarker 解析文本生成模板。
内建模板加载器
在 Configuration
中可以使用下面的方法来方便建立三种模板加载。
(每种方法都会在其内部新建一个模板加载器对象,然后创建
Configuration
实例来使用它。)
void setDirectoryForTemplateLoading(File dir);
或
void setClassForTemplateLoading(Class cl, String prefix);
或
void setServletContextForTemplateLoading(Object servletContext, String path);
上述的第一种方法在磁盘的文件系统上设置了一个明确的目录,
它确定了从哪里加载模板。不要说可能,File
参数肯定是一个存在的目录。否则,将会抛出异常。
第二种调用方法使用了一个 Class
类型的参数和一个前缀。这是让你来指定什么时候通过相同的机制来加载模板,
不过是用Java的 ClassLoader
来加载类。
这就意味着传入的class参数会被 Class.getResource()
用来调用方法来找到模板。参数 prefix
是给模板的名称来加前缀的。在实际运行的环境中,
类加载机制是首选用来加载模板的方法,通常情况下,从类路径下加载文件的这种机制,
要比从文件系统的特定目录位置加载安全而且简单。在最终的应用程序中,
所有代码都使用 .jar
文件打包也是不错的,
这样用户就可以直接执行包含所有资源的 .jar
文件了。
第三种调用方式需要Web应用的上下文和一个基路径作为参数,
这个基路径是Web应用根路径(WEB-INF
目录的上级目录)的相对路径。
那么加载器将会从Web应用目录开始加载模板。尽管加载方法对没有打包的
.war
文件起作用,因为它使用了
ServletContext.getResource()
方法来访问模板,
注意这里我们指的是“目录”。如果忽略了第二个参数(或使用了""
),
那么就可以混合存储静态文件(.html
,.jpg
等)
和 .ftl
文件,只是 .ftl
文件可以被送到客户端执行。
当然必须在 WEB-INF/web.xml
中配置一个Servlet来处理URI格式为
*.ftl
的用户请求,否则客户端无法获取到模板,
因此你将会看到Web服务器给出的秘密提示内容。在站点中不能使用空路径,这是一个问题,
你应该在 WEB-INF
目录下的某个位置存储模板文件,
这样模板源文件就不会偶然地被执行到,这种机制对servlet应用程序来加载模板来说,
是非常好用的方式,而且模板可以自动更新而不需重启Web应用程序,
但是对于类加载机制,这样就行不通了。
从多个位置加载模板
如果需要从多个位置加载模板,那就不得不为每个位置都实例化模板加载器对象,
将它们包装到一个称为 MultiTemplateLoader
的特殊模板加载器,
最终将这个加载器传递给 Configuration
对象的
setTemplateLoader(TemplateLoader loader)
方法。
下面给出一个使用类加载器从两个不同位置加载模板的示例:
import freemarker.cache.*; // template loaders live in this package ... FileTemplateLoader ftl1 = new FileTemplateLoader(new File("/tmp/templates")); FileTemplateLoader ftl2 = new FileTemplateLoader(new File("/usr/data/templates")); ClassTemplateLoader ctl = new ClassTemplateLoader(getClass(), ""); TemplateLoader[] loaders = new TemplateLoader[] { ftl1, ftl2, ctl }; MultiTemplateLoader mtl = new MultiTemplateLoader(loaders); cfg.setTemplateLoader(mtl);
现在,FreeMarker将会尝试从 /tmp/templates
目录加载模板,如果在这个目录下没有发现请求的模板,它就会继续尝试从
/usr/data/templates
目录下加载,如果还是没有发现请求的模板,
那么它就会使用类加载器来加载模板。
从其他资源加载模板
如果内建的类加载器都不适合使用,那么就需要来编写自己的类加载器了,
这个类需要实现 freemarker.cache.TemplateLoader
接口,
然后将它传递给 Configuration
对象的
setTemplateLoader(TemplateLoader loader)
方法。
可以阅读API JavaDoc文档获取更多信息。
如果模板需要通过URL访问其他模板,那么就不需要实现
TemplateLoader
接口了,可以选择子接口
freemarker.cache.URLTemplateLoader
来替代,
只需实现 URL getURL(String templateName)
方法即可。
模板名称(模板路径)
解析模板的名称(也就是模板路径)是由模板解析器来决定的。
但是要和其它对路径的格式要求很严格的组件一起使用。通常来说,
强烈建议模板加载器使用URL风格的路径。
在URL路径(或在UN*X路径)中符号有其它含义时,那么路径中不要使用
/
(路径分隔符)字符,.
(同目录符号)和..
(父目录符号)。字符
*
(星号)是被保留的,
它用于FreeMarker的 "模板获取" 特性。
://
(或者使用
template_name_format
配置设置到
DEFAULT_2_4_0
,:
(冒号)
字符)是被保留用来指定体系部分的,和URI中的相似。比如
someModule://foo/bar.ftl
使用
someModule
,或者假定 DEFAULT_2_4_0
格式,classpath:foo/bar.ftl
使用
classpath
体系。解释体系部分完全由
TemplateLoader
决定。
(FreeMarker核心仅仅知道体系的想法,否则它不能正常处理相对模板名称。)
FreeMarker通常在将路径传递到 TemplateLoader
之前把它们正常化,所以路径中不会包含 /../
这样的内容,
路径会相对于虚构的模板根路径(也就是它们不会以 /
开头)。
其中也不会包含 *
,因为模板获取发生在很早的阶段。
此外,将 template_name_format
设置为
DEFAULT_2_4_0
,多个连续的 /
将会被处理成单独的 /
(除非它们是 ://
模式分隔符的一部分)。
请注意,不管主机运行的操作系统是什么, FreeMarker 模板加载时经常使用斜线(而不是反斜线)。
模板缓存
FreeMarker 是会缓存模板的(假设使用 Configuration
对象的方法来创建 Template
对象)。这就是说当调用
getTemplate
方法时,FreeMarker不但返回了
Template
对象,而且还会将它存储在缓存中,
当下一次再以相同(或相等)路径调用 getTemplate
方法时,
那么它只返回缓存的 Template
实例,
而不会再次加载和解析模板文件了。
如果更改了模板文件,当下次调用模板时,FreeMarker 将会自动重新载入和解析模板。
然而,要检查模板文件是否改变内容了是需要时间的,有一个
Configuration
级别的设置被称作"更新延迟",它可以用来配置这个时间。
这个时间就是从上次对某个模板检查更新后,FreeMarker再次检查模板所要间隔的时间。
其默认值是5秒。如果想要看到模板立即更新的效果,那么就要把它设置为0。
要注意某些模板加载器也许在模板更新时可能会有问题。
例如,典型的例子就是在基于类加载器的模板加载器就不会注意到模板文件内容的改变。
当调用了 getTemplate
方法时,
与此同时FreeMarker意识到这个模板文件已经被移除了,所以这个模板也会从缓存中移除。
如果Java虚拟机认为会有内存溢出时,默认情况它会从缓存中移除任意模板。
此外,你还可以使用 Configuration
对象的
clearTemplateCache
方法手动清空缓存。
何时将一个被缓存了的模板清除的实际应用策略是由配置的属性
cache_storage
来确定的,通过这个属性可以配置任何
CacheStorage
的实现。对于大多数用户来说,
使用 freemarker.cache.MruCacheStorage
就足够了。
这个缓存存储实现了二级最近使用的缓存。在第一级缓存中,
组件都被强烈引用到特定的最大数目(引用次数最多的组件不会被Java虚拟机抛弃,
而引用次数很少的组件则相反)。当超过最大数量时,
最近最少使用的组件将被送至二级缓存中,在那里它们被很少引用,
直到达到另一个最大的数目。引用强度的大小可以由构造方法来指定。
例如,设置强烈部分为20,轻微部分为250:
cfg.setCacheStorage(new freemarker.cache.MruCacheStorage(20, 250))
或者,使用 MruCacheStorage
缓存,
它是默认的缓存存储实现:
cfg.setSetting(Configuration.CACHE_STORAGE_KEY, "strong:20, soft:250");
当创建了一个新的 Configuration
对象时,
它使用一个 strongSizeLimit
值为0的
MruCacheStorage
缓存来初始化,
softSizeLimit
的值是 Integer.MAX_VALUE
(也就是在实际中,是无限大的)。但是使用非0的 strongSizeLimit
对于高负载的服务器来说也许是一个更好的策略,对于少量引用的组件来说,
如果资源消耗已经很高的话,Java虚拟机往往会引发更高的资源消耗,
因为它不断从缓存中抛出经常使用的模板,这些模板还不得不再次加载和解析。