模板加载

模板加载器

模板加载器是加载基于抽象模板路径下,比如 "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虚拟机往往会引发更高的资源消耗, 因为它不断从缓存中抛出经常使用的模板,这些模板还不得不再次加载和解析。