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行为的已被命名的值。配置设置有很多, 例如:locale
,number_format
, default_encoding
, template_exception_handler
。
配置设置存储在 Configuration
实例中,可以在 Template
实例中被覆盖。比如,在配置中给 locale
设置为 "en_US"
, 那么使用该配置的所有模板中的 locale
都使用 "en_US"
, 除非在模板中locale被明确地设置成其它不同的值(参见 localization)。 因此,在 Configuration
中的值充当默认值, 这些值在每个模板中也可以被覆盖。在 Configuration
或 Template
实例中的值也可以在单独调用 Template.process
方法后被覆盖。 对于每个调用了 freemarker.core.Environment
对象的值在内部创建时就持有模板执行的运行时环境,也包括了那个级别被覆盖了的设置信息。 在模板执行时,那里存储的值也可以被改变,所以模板本身也可以设置配置信息, 比如在输出中途来变换 locale
设置。
配置信息可以被想象成3层(Configuration
, Template
,Environment
), 最高层包含特定的值,它为设置信息提供最有效的值。 比如(设置信息A到F仅仅是为这个示例而构想的):
Setting A | Setting B | Setting C | Setting D | Setting E | Setting 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
,或者在你获取它的时候将抛出异常。
我们看看如何准确设置配置信息:
Configuration
层: 原则上设置配置信息时使用Configuration
对象的setter方法,例如:1 Configuration myCfg = new Configuration(Configuration.VERSION_2_3_23); 2 myCfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); 3 myCfg.setDefaultEncoding("UTF-8");
在真正使用
Configuration
对象 (通常在初始化应用程序时)之前来配置它,后面必须将其视为只读的对象。在实践中,比如很多Web应用框架中,就应该使用这种框架特定的配置方式来进行配置, 比如使用成对的
String
来配置(像在.properties
属性配置文件中那样)。 在这种情况下,框架的作者大多数使用Configuration
对象的setSetting(String name, String value)
方法。 这可以参考setSetting
的API文档 部分来获取可用的设置名和参数的格式的信息。 而在Spring框架中,我们可以这样进行:1 <bean id="freemarkerConfig" 2 class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> 3 <property name="freemarkerSettings"> 4 <props> 5 <prop key="incompatible_improvements">2.3.23</prop> 6 <prop key="template_exception_handler">rethrow</prop> 7 <prop key="default_encoding">UTF-8</prop> 8 </props> 9 </property> 10 </bean>
请注意,这种形式的配置(
String
键-值对) 和直接使用Configuration
的API相比, 很不幸地被限制了。Template
层:对于被请求的本地化信息,模板的locale
设置由Configuration.getTemplate(...)
来设置。 否则,就不能在这里进行设置,除非想控制Template
对象来代替freemarker.cache.TemplateCache
,这样的话, 应该在Template
对象第一次被使用前就设置配置信息, 然后就将Template
对象视为是只读的。Environment
层:这里有两种配置方法:- 使用Java API:使用
Environment
对象的setter方法。当然想要在模板执行之前来做,然后当调用myTemplate.process(...)
时会遇到问题, 因为在内部创建Environment
对象后立即就执行模板了, 导致没有机会来进行设置。这个问题的解决可以用下面两个步骤进行:1 Environment env = myTemplate.createProcessingEnvironment(root, out); 2 env.setLocale(java.util.Locale.ITALY); 3 env.setNumberFormat("0.####"); 4 env.process(); // process the template
- 在模板中(通常这被认为是不好的做法)直接使用
setting
指令,例如:1 <#setting locale="it_IT"> 2 <#setting number_format="0.####">
在这层,当什么时候改变配置信息,是没有限制的。
- 使用Java API:使用
要知道 FreeMarker 支持什么样的配置信息还有它们的意义, 可以先看看FreeMarker Java API文档中的下面这部分内容:
- 在三层中
freemarker.core.Configurable
的setter方法来配置。 - 只在
Configuration
层可用的freemarker.template.Configuration
的setter方法来配置。 - 在三层中可用
String
键-值对书写的freemarker.core.Configurable.setSetting(String,String)
配置。 - 只在
Configuration
层中可用String
键-值对书写的freemarker.template.Configuration.setSetting(String, String)
配置。
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 发生的异常,可以分为如下几类:
- 当配置 FreeMarker 时发生异常:典型地情况,就是在应用程序初始化时, 仅仅配置了一次 FreeMarker。在这个过程中,异常就会发生, 从 FreeMarker 的API中,我们可以很清楚的看到这一点...
- 当加载和解析模板时发生异常:调用了
Configuration.getTemplate(...)
方法, FreeMarker就要把模板文件加载到内存中然后来解析它 (除非模板已经在Configuration
对象中被 缓存 了)。 在这期间,有两种异常可能发生:- 因模板文件没有找到而发生的
IOException
异常, 或在读取文件时发生其他的I/O问题。比如没有读取文件的权限,或者是磁盘错误。 这些错误的发出者是TemplateLoader
对象,可以将它作为插件设置到Configuration
对象中。(为了正确起见:这里所说的”文件”, 是简化形式。例如,模板也可以存储在关系型数据库的表中。这是TemplateLoader
所要做的事。) - 根据FTL语言的规则,模板文件发生语法错误时会导致
freemarker.core.ParseException
异常。当获得Template
对象 (Configuration.getTemplate(...)
)时, 这种错误就会发生,而不是当执行 (Template.process(...)
)模板的时候。 这种异常是IOException
的一个子类。
- 因模板文件没有找到而发生的
- 当执行(处理)模板时发生的异常,也就是当调用了
Template.process(...)
方法时会发生的两种异常:- 当试图写入输出对象时发生错误而导致的
IOException
异常。 - 当执行模板时发生的其它问题而导致的
freemarker.template.TemplatException
异常。 比如,一个频繁发生的错误,就是当模板引用一个不存在的变量。 默认情况下,当TemplatException
异常发生时, FreeMarker会用普通文本格式在输出中打印出FTL的错误信息和堆栈跟踪信息, 然后再次抛出TemplatException
异常而中止模板的执行, 就可以捕捉到Template.process(...)
方法抛出的异常了。而这种行为是可以定制的。FreeMarker也会经常写TemplatException
异常的 日志。
- 当试图写入输出对象时发生错误而导致的
根据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 本身带有这些预先编写的错误控制器:
TemplateExceptionHandler.DEBUG_HANDLER
: 打印堆栈跟踪信息(包括FTL错误信息和FTL堆栈跟踪信息)和重新抛出的异常。 这是默认的异常控制器(也就是说,在所有新的Configuration
对象中,它是初始化好的)。TemplateExceptionHandler.HTML_DEBUG_HANDLER
: 和DEBUG_HANDLER
相同,但是它可以格式化堆栈跟踪信息, 那么就可以在Web浏览器中来阅读错误信息。 当你在制作HTML页面时,建议使用它而不是DEBUG_HANDLER
。TemplateExceptionHandler.IGNORE_HANDLER
: 简单地压制所有异常(但是要记住,FreeMarker 仍然会写日志)。 它对处理异常没有任何作用,也不会重新抛出异常。TemplateExceptionHandler.RETHROW_HANDLER
: 简单重新抛出所有异常而不会做其它的事情。 这个控制器对Web应用程序(假设你在发生异常之后不想继续执行模板)来说非常好, 因为它在生成的页面发生错误的情况下,给了你很多对Web应用程序的控制权 (因为FreeMarker不向输出中打印任何关于该错误的信息)。 要获得更多在Web应用程序中处理错误的信息,可以 参见FAQ。
在模板中明确地处理错误
尽管它和 FreeMarker 的配置(本章的主题)无关,但是为了说明的完整性, 在这里提及一下,你可以在模板中直接控制错误。 通常这不是一个好习惯(尽量保持模板简单,技术含量不要太高),但有时仍然需要:
- 处理不存在/为空的变量: 模板开发指南/模板/表达式/处理不存在的值
- 在发生障碍的"porlets"中留存下来,还可以扩展参考: 模板语言参考 /指令参考/attempt, recover
6.“不兼容改进”设置
它要做什么
该设置指定了 FreeMarker 的版本号,那么就不会100%向后兼容bug修复和改进 你想要启用 已经实现的内容。 通常来说,默认把它留在2.3.0(最大向后兼容版本)是一个坏主意。
在新项目中,应该将它设置为实际使用的FreeMarker版本号。 而在老项目中,那么最好也将它设置的高一些,最好检查一下哪些修改是激活状态,至少不仅仅是 "不兼容改进" 第三版本号(小版本)设置提高。通常来讲, 只要为该设置增加最后的版本号,那么这些修改的风险就会小很多。
Bug修复和改进是完全向后兼容的,同样,它们也是重要的安全更新, 不管 "不兼容改进" 如何设置都是启用的。
该设置的一个重要结果是应用程序可以检查声明的 FreeMarker 最小版本需求是否达到。 比如你设置了2.3.22,但是应用程序却意外部署到了 FreeMarker 2.3.21, 那么FreeMarker就会出问题,告诉你需要一个更高的版本。 最后,请求的修复/改进不会作用于低版本。
如何设置
这个不兼容改进的设置在 Configuration
级别。 它可以在多种方式下设置(假设我们想将它设置为2.3.22):
- 创建
freemarker.template.Configuration
对象,比如:... = new Configuration(Configuration.VERSION_2_3_22)
- 或者,使用初始化设置来更改
Configuration
单例,比如:cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_22)
- 或者,如果使用properties文件来配置FreeMarker (
*.properties
文件或者java.util.Properties
对象),添加:incompatible_improvements=2.3.22
- 或者,如果通过
FreemarkerServlet
来配置FreeMarker,那么将init-param
添加到web.xml
中:<init-param> <param-name>incompatible_improvements</param-name> <param-value>2.3.22</param-value> </init-param>
但是, 在应用程序中 如果设置了 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