freeMarker(八)——程序开发指南之配置(Configuration)

时间:2017-01-17 15:16:36   收藏:0   阅读:6613

1.基本内容

  配置(configuration)就是 freemarker.template.Configuration 对象, 它存储了常用(全局,应用程序级)的设置,定义了想要在所有模板中可用的变量(称为共享变量)。 而且,它会处理 Template 实例的新建和缓存。

  应用程序典型的用法是使用一个独立的共享 Configuration 实例。更精确来说, 典型的做法是每一个独立开发的组件(比如项目,模块等)都有一个 Configuration 实例,它在内部使用FreeMarker, 每一个都创建它自己的实例。

  运行中的模板会受配置设置的影响,每个 Template 实例通过对应 Template 构造方法参数,都有和它相关联的 Configuration 实例。通常可以使用 Configuration.getTemplate (而不是直接调用 Template 的构造方法)来获得 Template 实例,此时,关联的 Configuration 实例就是调用 getTemplate 方法的。

2.共享变量

  Shared variables (共享变量)是为所有模板定义的变量。可以使用 setSharedVariable 方法向配置中添加共享变量:

1 Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
2 ...
3 cfg.setSharedVariable("warp", new WarpDirective());
4 cfg.setSharedVariable("company", "Foo Inc.");

  在所有使用这个配置的模板中,名为 wrap 的用户自定义指令和一个名为 company 的字符串将会在数据模型的根root上可见, 那就不用在根哈希表上一次又一次的添加它们。在传递给 Template.process 的 根root对象里的变量将会隐藏同名的共享变量。

警告:如果配置对象在多线程环境中使用,不要使用 TemplateModel 实现类来作为共享变量, 因为它是不是线程安全的! 这也是基于Servlet应用程序的典型情形。

  出于向后兼容的特性,共享变量的集合初始化时 (就是对于新的 Configuration 实例来说)不能为空。 它包含下列用户自定义指令(用户自定义指令使用时需要用 @ 来代替#):

名称
capture_output freemarker.template.utility.CaptureOutput
compress freemarker.template.utility.StandardCompress
html_escape freemarker.template.utility.HtmlEscape
normalize_newlines freemarker.template.utility.NormalizeNewlines
xml_escape freemarker.template.utility.XmlEscape

3.配置设置

  Settings(配置设置) 是影响FreeMarker行为的已被命名的值。配置设置有很多, 例如:localenumber_formatdefault_encodingtemplate_exception_handler

  配置设置存储在 Configuration 实例中,可以在 Template 实例中被覆盖。比如,在配置中给 locale 设置为 "en_US", 那么使用该配置的所有模板中的 locale 都使用 "en_US", 除非在模板中locale被明确地设置成其它不同的值(参见 localization)。 因此,在 Configuration 中的值充当默认值, 这些值在每个模板中也可以被覆盖。在 ConfigurationTemplate 实例中的值也可以在单独调用 Template.process 方法后被覆盖。 对于每个调用了 freemarker.core.Environment 对象的值在内部创建时就持有模板执行的运行时环境,也包括了那个级别被覆盖了的设置信息。 在模板执行时,那里存储的值也可以被改变,所以模板本身也可以设置配置信息, 比如在输出中途来变换 locale 设置。

  配置信息可以被想象成3层(ConfigurationTemplateEnvironment), 最高层包含特定的值,它为设置信息提供最有效的值。 比如(设置信息A到F仅仅是为这个示例而构想的):

 Setting ASetting BSetting CSetting DSetting ESetting F
Layer 3: Environment 1 - - 1 - -
Layer 2: Template 2 2 - - 2 -
Layer 1: Configuration 3 3 3 3 - -

  配置信息的有效值为:A=1,B=2,C=3,D=1,E=2。 而F的设置则是 null,或者在你获取它的时候将抛出异常。

  我们看看如何准确设置配置信息:

  要知道 FreeMarker 支持什么样的配置信息还有它们的意义, 可以先看看FreeMarker Java API文档中的下面这部分内容:

4.模板加载

模板加载器

  模板加载器是加载基于抽象模板路径下,比如 "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)方法。 下面给出一个使用类加载器从两个不同位置加载模板的示例:

 1 import freemarker.cache.*; // template loaders live in this package
 2 
 3 ...
 4 
 5 FileTemplateLoader ftl1 = new FileTemplateLoader(new File("/tmp/templates"));
 6 FileTemplateLoader ftl2 = new FileTemplateLoader(new File("/usr/data/templates"));
 7 ClassTemplateLoader ctl = new ClassTemplateLoader(getClass(), "");
 8 TemplateLoader[] loaders = new TemplateLoader[] { ftl1, ftl2, ctl };
 9 MultiTemplateLoader mtl = new MultiTemplateLoader(loaders);
10 
11 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虚拟机往往会引发更高的资源消耗, 因为它不断从缓存中抛出经常使用的模板,这些模板还不得不再次加载和解析。

5.错误控制

可能的异常

  关于 FreeMarker 发生的异常,可以分为如下几类:

根据TemplateException来自定义处理方式

  TemplateException 异常在模板处理期间的抛出是由 freemarker.template.TemplateExceptionHandler 对象控制的,这个对象可以使用 setTemplateExceptionHandler(...) 方法配置到 Configuration 对象中。 TemplateExceptionHandler 对象只包含一个方法:

void handleTemplateException(TemplateException te, Environment env, Writer out) throws TemplateException;

  无论 TemplateException 异常什么时候发生,这个方法都会被调用。 异常处理是传递的 te 参数控制的, 模板处理的运行时(Runtime,译者注)环境可以访问 env 变量, 处理器可以使用 out 变量来打印输出信息。 如果方法抛出异常(通常是重复抛出 te),那么模板的执行就会中止, 而且 Template.process(...) 方法也会抛出同样的异常。如果 handleTemplateException 对象不抛出异常,那么模板将会继续执行,就好像什么也没有发生过一样, 但是引发异常的语句将会被跳过(后面会详细说)。 当然,控制器仍然可以在输出中打印错误提示信息。

  任何一种情况下,当 TemplateExceptionHandler 被调用前, FreeMarker 将会记录异常日志

  我们用实例来看一下,当错误控制器不抛出异常时, FreeMarker是如何跳过出错‘‘语句‘‘的。假设我们已经使用了如下模板异常控制器:

 1 class MyTemplateExceptionHandler implements TemplateExceptionHandler {
 2     public void handleTemplateException(TemplateException te, Environment env, java.io.Writer out)
 3             throws TemplateException {
 4         try {
 5             out.write("[ERROR: " + te.getMessage() + "]");
 6         } catch (IOException e) {
 7             throw new TemplateException("Failed to print error message. Cause: " + e, env);
 8         }
 9     }
10 }
11 
12 ...
13 
14 cfg.setTemplateExceptionHandler(new MyTemplateExceptionHandler());

  如果错误发生在非FTL标记(没有被包含在 <#...><@...>之间)的插值中, 那么整个插值将会被跳过。那么下面这个模板 (假设 badVar 在数据模型中不存在):

a${badVar}b

  如果我们使用了 MyTemplateExceptionHandler,就会打印:

a[ERROR: Expression badVar is undefined on line 1, column 4 in test.ftl.]b

  而下面这个模板也会打印相同信息(除了报错的列数位置会不同...):

a${"moo" + badVar}b

  因为像这样来写时,只要插值内发生任何错误,整个插值都会被跳过。

  如果错误发生在指令调用中参数的计算时,或者是指令参数列表发生问题时, 或在<@exp ...>中计算 exp时发生错误,或者 exp不是用户自定义的指令, 那么整个指令调用都会被跳过。例如:

a<#if badVar>Foo</#if>b

  将会输出:

a[ERROR: Expression badVar is undefined on line 1, column 7 in test.ftl.]b

  请注意,错误发生在 if 指令的开始标签 (<#if badVar>)中,但是整个指令的调用都被跳过了。 从逻辑上说,嵌套的内容(Foo)也被跳过了, 因为嵌套的内容是受被包含的指令(if)控制(打印)的。

  下面这个的输出也是相同的(除了报错的列数会不同...):

a<#if "foo${badVar}" == "foobar">Foo</#if>b

  因为,正如这样来写,在参数处理时发生任何一个错误, 整个指令的调用都将会被跳过。

  如果错误发生在已经开始执行的指令之后,那么指令调用将不会被跳过。 也就是说,如果在嵌套的内容中发生任何错误:

1 a
2 <#if true>
3   Foo
4   ${badVar}
5   Bar
6 </#if>
7 c

  或者在一个宏定义体内:

1 a
2 <@test />
3 b
4 <#macro test>
5   Foo
6   ${badVar}
7   Bar
8 </#macro>

  那么输出将会是:

a
  Foo
  [ERROR: Expression badVar is undefined on line 4, column 5 in test.ftl.]
  Bar
c

  FreeMarker 本身带有这些预先编写的错误控制器:

在模板中明确地处理错误

  尽管它和 FreeMarker 的配置(本章的主题)无关,但是为了说明的完整性, 在这里提及一下,你可以在模板中直接控制错误。 通常这不是一个好习惯(尽量保持模板简单,技术含量不要太高),但有时仍然需要:

6.“不兼容改进”设置

它要做什么

  该设置指定了 FreeMarker 的版本号,那么就不会100%向后兼容bug修复和改进 你想要启用 已经实现的内容。 通常来说,默认把它留在2.3.0(最大向后兼容版本)是一个坏主意。

  在新项目中,应该将它设置为实际使用的FreeMarker版本号。 而在老项目中,那么最好也将它设置的高一些,最好检查一下哪些修改是激活状态,至少不仅仅是 "不兼容改进" 第三版本号(小版本)设置提高。通常来讲, 只要为该设置增加最后的版本号,那么这些修改的风险就会小很多。

  Bug修复和改进是完全向后兼容的,同样,它们也是重要的安全更新, 不管 "不兼容改进" 如何设置都是启用的。

  该设置的一个重要结果是应用程序可以检查声明的 FreeMarker 最小版本需求是否达到。 比如你设置了2.3.22,但是应用程序却意外部署到了 FreeMarker 2.3.21, 那么FreeMarker就会出问题,告诉你需要一个更高的版本。 最后,请求的修复/改进不会作用于低版本。

如何设置

  这个不兼容改进的设置在 Configuration 级别。 它可以在多种方式下设置(假设我们想将它设置为2.3.22):

  但是, 在应用程序中 如果设置了 object_wrapper (也就是 Configuration.setObjectWrapper(ObjectWrapper)), 那么要知道很重要的一点,BeansWrapper 和它的子类(最重要的是, DefaultObjectWrapper) 有它们自己独立的 incompatibleImprovements 属性,还有一些修复/改进会被它激活, 而不是通过 Configuration 的相似设置。 如果你没有在任何地方设置过 object_wrapper,那么不需要知道这点, 因为,和 Configuration 一样, 默认的 object_wrapper 也有相同的 "不兼容改进"。 但是,如果设置了 object_wrapper, 那么就不要忘了设置 ObjectWrapper 自己的 incompatibleImprovements 属性,此外还有 Configuration。(请注意,对于 Configuration 来说, 有不同的 "不兼容改进" 也是可以的,而对于 ObjectWrapper, 则有它自己的选择。) 至于 DefaultObjectWrapper (对于 BeansWrapper 也是一样的,只是不同的类名而已) 参看这里如何来设置它

 

译自 Email: ddekany at users.sourceforge.net

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!