FAQ

FreeMarker Pros:

  • FreeMarker 不受 Servlet 或网络/ Web 的约束;它只是一个类库,用于通过将模板与 Java 对象(数据模型)合并来生成文本输出。您可以随时随地执行模板;不需要 HTTP 请求转发或类似的技巧,也完全不需要 Servlet 环境。因此,您可以轻松地将其集成到任何系统中。

  • Terser 语法。考虑以下 JSP(假设<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>):

<c:if test="${t}">
  True
</c:if>

<c:choose>
  <c:when test="${n == 123}">
      Do this
  </c:when>
  <c:otherwise>
      Do that
  </c:otherwise>
</c:choose>

<c:forEach var="i" items="${ls}">
- ${i}
</c:forEach>

和等效的 FTL:

<#if t>
  True
</#if>

<#if n == 123>
  Do this
<#else>
  Do that
</#if>

<#list ls as i>
- ${i}
</#list>
  • Auto-escaping option转义以${...}打印的 HTML 和 XML 特殊字符。因此,您可以只写${x}而不是<c:out value="${x}"/>,最重要的是,您不能偶然忘记进行转义。

  • 默认情况下,区域设置敏感的数字和日期格式。当输出给 Listener 时,您只需要写${x}而不是<fmt:formatNumber value="${x}" />即可。

  • 模板中没有 servlet 特定的作用域和其他高度技术性的内容(当然,除非您有意将它们公开到数据模型中)。它是从一开始就为 MVC 制作的,仅专注于演示。

  • 您可以从任何地方加载模板。从 Classpath,从数据库等

  • 更容易定义临时宏和函数。

  • 地毯下面没有清扫错误。缺少变量和null -s 不会默认为0/false/empty-string,但会导致错误。 在这里查看有关此的更多信息...

  • “对象包装”。这使您可以以定制的,面向表示的方式将对象显示到模板(例如see here使用该技术的模板如何显示 W3C DOM 节点)。

  • 宏和函数只是变量,因此可以像其他任何值一样轻松地将它们作为参数值传递,放入数据模型等中。

  • 第一次(或更改页面后)访问页面时,几乎没有明显的延迟,因为没有昂贵的编译发生。

FreeMarker Cons:

  • 不是“标准”。工具和 IDE 集成的数量减少了,开发人员知道的更少了,总体上,行业支持也更少了。 (但是,除非基于.tag文件,否则大多数 JSP 标记库都可以在 FreeMarker 模板中以适当的设置工作.)

  • 除了某些视觉上的相似性外,它的语法不遵循 HTML/XML 规则,这对于新用户来说是令人困惑的(这是简洁的代价)。 JSP 也不遵循它,但是更接近它。

  • 由于宏和函数仅仅是变量,因此只能在运行时才能检测到错误的指令和参数名称以及缺少的必需参数。

  • 不适用于 JSF。 (它在技术上可以正常工作,但尚未实现.)

如果您正在考虑在现有应用程序或仅支持 JSP 的旧框架中使用 FreeMarker 替换 JSP,则可以阅读以下内容:程序员指南/其他/将 FreeMarker 与 servlet 一起使用/将 FreeMarker 用于“模型 2”

  • 2.为什么 FreeMarker 对null -s 和缺少的变量如此挑剔,以及如何处理?

首先,您应该了解挑剔的原因。大多数脚本语言和模板语言都可以忽略缺失的变量(以及null -s),并且通常将它们视为空字符串和/或 0 和/或逻辑假。此行为有几个问题:

  • 它可能会隐藏意外错误,例如变量名中的错字,或者模板作者引用了程序员没有将其放入该模板的数据模型中的变量,或者程序员使用了不同的名称。人们很容易犯这样的错误,而计算机却没有,所以错过这个机会使模板引擎能够显示这些错误是一件坏事。即使您在开发过程中非常仔细地检查了模板的输出,还是很容易查看诸如<#if hasWarnigs>print warnings here...</#if>之类的错误,因为您 Importing 了错误的变量名(您注意到了吗?),所以它们将在无提示的情况下从不打印警告。在以后修改应用程序时,还要考虑维护。可能您不会针对所有可能的情况每次都仔细检查模板(许多应用程序有数百个模板)。单元测试通常也不会很好地覆盖网页内容(如果您完全拥有它们……);他们通常只检查网页中某些手动设置的模式,因此它们通常会掩盖实际上是错误的更改。但是,如果页面异常失败,那就是人类测试人员会注意到的事情,而单元测试则会注意到(因为整个页面都会失败),而在 Producing,维护人员会注意到(假设有人检查错误日志)。

  • 做出危险的假设。脚本语言或模板引擎对应用程序域一无所知,因此当它决定一个它不知道的值为 0/false 时,这是一件很不负责任的事情。仅仅因为它不知道您银行的当前余额是多少,我们可以说是 0 美元吗?仅仅因为不知道患者是否患有青霉素过敏,我们能说他/她没有吗?只需考虑此类错误的含义即可。显示错误页面通常比显示不正确的信息更好,从而导致用户做出错误的决定。

在这种情况下,不挑剔通常会笼罩整个地毯(不面对问题),这当然会使大多数人感到更方便,但是,我们仍然认为,在大多数情况下,严格要求可以节省时间并提高软件质量。长运行。

另一方面,我们认识到在某些情况下您不希望 FreeMarker 如此挑剔,并为他们提供了解决方案:

  • 通常,您的数据模型包含null -s 或具有可选变量。在这种情况下,请使用these operators。如果您经常使用它们,请尝试重新考虑您的数据模型,因为过多依赖它们不仅会使模板过于冗长,而且会增加隐藏错误和打印任意错误输出的可能性(由于上述原因) 。

  • 在某些应用程序中,您可能希望显示不完整/损坏的页面,而不是错误页面。在这种情况下,您可以比默认值使用另一个错误处理程序。自定义错误处理程序可以跳过有问题的部分,或在那里显示错误指示符,而不是中止整个页面的呈现。但是请注意,尽管错误处理程序不会为变量提供任意默认值,但对于显示关键信息的页面,最好还是显示错误页面。

  • 如果页面包含的部分不是很重要(例如一些侧栏),则您可能感兴趣的另一个功能是尝试/恢复指令

  • 3.为什么 FreeMarker 用奇怪的格式打印数字(如 1,000,000 或 1,000,000 而不是 1000000)?

    • FreeMarker 使用 Java 平台的区域设置敏感的数字格式设置功能。您的语言环境的默认数字格式可以使用分组或其他格式。如果您不想这样做,则必须使用number_format FreeMarker setting覆盖 Java 平台建议的数字格式。例如:
cfg.setNumberFormat("0.######");  // now it will print 1000000
// where cfg is a freemarker.template.Configuration object

但是请注意,比起人类,经常会发现不对分隔符进行分组就很难读取大数字。因此,通常建议保留它们,并且在数字用于“计算机 Spectator”的情况下(在分组分隔符上混淆),请使用c built-in。例如:

<a href="/shop/productdetails?id=${product.id?c}">Details...</a>

对于计算机用户,无论如何您都需要?c,因为根据区域设置,小数点分隔符也可能会引起注意。

  • 4.为什么 FreeMarker 打印错误的小数点和/或分组分隔符(如 3.14 而不是 3,14)?
    • 不同的国家使用不同的十进制/分组分隔符。如果看到不正确的符号,则可能是您的语言环境设置不正确。设置 JVM 的默认语言环境或使用locale FreeMarker setting覆盖默认语言环境。例如:
cfg.setLocale(java.util.Locale.ITALY);
// where cfg is a freemarker.template.Configuration object

但是,有时您希望输出的数字不是面向人类的,而是针对“计算机的受众”的(例如,您要在 CSS 中打印尺寸),在这种情况下,无论使用哪种语言环境(语言),都必须使用点作为小数点分隔符页面。为此,请使用c built-in,例如:

font-size: ${fontSize?c}pt;
  • 5.当我尝试打印${aBoolean}之类的布尔值时,为什么 FreeMarker 会给出错误,以及如何解决?
    • ${...}旨在格式化供人类使用的值,并且与数字不同,布尔值没有通用的格式(true/false在计算机语言中很常见,但您很少在其之外使用它)。正确的格式在很大程度上取决于上下文,因此,通常模板作者应为每种情况决定正确的格式,例如${washable?string("yes", "no")}${caching?string("Enabled", "Disabled")}${heating?string("on", "off")}等。

但是,有两种情况不切实际:

  • 当打印布尔值以生成计算机语言输出时,因此您需要true/false。在这种情况下,请使用${someBoolean?c}(需要 FreeMarker 2.3.20)。如果您从不为人类消费而生成,而只是为计算机语言输出而生成,则可能需要将boolean_format设置为c(自 FreeMarker 2.3.29 起可用),然后${aBoolean}将表现为${aBoolean?c}

在 2.3.20 之前,如果您确实无法升级 FreeMarker,请尝试使用${someBoolean?string}。默认情况下打印true/false。然后以某种方式尝试确保boolean_format稍后不会被某人更改,从而破坏您的输出。

  • 当您使用相同的方式格式化大多数布尔值时。在这种情况下,您可以设置boolean_format设置(Configuration.setBooleanFormat)来反映这一点,然后从 FreeMarker 2.3.20 开始,您只需编写${someBoolean}即可。 (请注意,这对于true/false无效,但您必须在其中使用?c.)

    1. FreeMarker 找不到我的模板(TemplateNotFoundExceptionFileNotFoundException,“未找到模板”错误消息)
    • 首先,您应该知道 FreeMarker 不会直接从文件系统路径加载模板。相反,它使用一个简单的虚拟文件系统,该文件系统可能会读取非文件系统资源(从 jar-s 内部,数据库表内部等中读取模板)。该虚拟文件由配置设置Configuration.setTemplateLoader(TemplateLoader)决定。即使您使用的TemplateLoaderMap 到文件系统,它也会有一个包含所有模板的基本目录,并且这将是您无法访问的虚拟文件系统的根目录(即,绝对路径将仍然相对于虚拟文件系统根目录)。

解决问题的提示:

  • 如果您是配置 FreeMarker 的人,请确保设置正确的TemplateLoader

  • 否则,请查看模板未找到错误消息是否包含所使用的TemplateLoader的描述。如果不是,则说明您使用的是 FreeMarker 的旧版本,请对其进行更新。获取FileNotFoundException而不是TemplateNotFoundException也是一个迹象,因此您将收到较少有用的错误消息。 (如果错误消息中的TemplateLoader类似于foo.SomeTemplateLoader@64f6106c,因此未显示一些相关参数,则可能应要求作者定义一个更好的toString().)

  • 一个常见的错误是对基于 Servlet 的 Web 应用程序使用FileTemplateLoader而不是WebappTemplateLoader。它可能在一种环境中工作,但在另一种环境中工作,因为 Servlet 规范不保证您的资源可以作为纯文件访问,即使提取了war文件也不保证。

  • 要知道,当您从另一个模板中包含一个模板/从另一个模板导入一个模板时,如果您不以/开头,则该模板名称将相对于包含模板的目录进行解释。错误消息包含全名(已解析),因此您应该在此注意。

  • 检查您使用的不是\(反斜杠),而不是/(斜杠)。 (FreeMarker 2.3.22 及更高版本将在错误消息中对此进行警告.)

  • 作为最后的选择,请在类别freemarker.cache上打开调试级别的日志记录(在您使用的日志记录框架中),以查看正在发生的事情。

  • 7.文档中写有* X *功能,但 FreeMarker 似乎不知道该功能,或者它的行为方式与所记录的不同,或者仍然存在应该修复的错误。

    • 您确定使用的是与实际使用的 FreeMarker 相同版本的文档吗?特别是,请注意,我们的在线文档适用于最新的 FreeMarker 稳定发行版。您可以使用旧版本;更新它。

您确定 Java 类加载器找到与期望使用的相同的freemarker.jar吗?也许周围有一个较旧的freemarker.jar版本,它永远不会消失。要检查这一点,请尝试使用${.version}在模板中打印版本号。 (如果死于“未知的内置变量:版本”错误消息,则您使用的是非常非常旧的发行版.)

如果您怀疑问题在于您有多个freemarker.jar -s,则典型的罪魁祸首是某个模块具有 Maven 或 Ivy 依赖项,并且具有旧的freemarker组 ID,而不是更现代的org.freemarker组 ID。由于组 ID 不同,因此 Maven 或 Ivy 不会将它们视为冲突的工件,因此这两个版本都可以使用。在这种情况下,您必须排除freemarker依赖项。

如果您认为文档或 FreeMarker 错误,请使用错误跟踪器或邮件列表进行报告。谢谢!

    1. FreeMarker 标记的<>混淆了我的编辑器或 XML 解析器。该怎么办?
    • 您可以使用[]代替<>;查看有关方括号标记语法here.的更多信息。比较运算符(如<)也具有其他语法(在这种情况下为lt&lt;);进一步了解他们here。另外,自 2.3.27 起,&&运算符(格式不正确的 HTML/XML)可以写为\and&amp;&amp;
  • 9.我必须生成很多输出中使用${...}和/或#{...},FreeMarker 尝试解决它们。该怎么办?

    • 您可以像${'$'}{...}一样逃避它们,但是如果必须经常这样做,这是不切实际的。因此,从 FreeMarker 2.3.28 开始,您可以使用[=... ]来代替;查看有关方括号内插语法here.的更多信息。如果要生成 JSP 文件甚至 FreeMarker 模板,这将非常有用。
  • 11.如何使用包含减号(-),冒号(:),点(.)或其他特殊字符的变量名称(宏名称,参数名称)?

    • 如果您有一个名称很奇怪的变量,例如“ foo-bar”,则 FreeMarker 会误解您只是在${foo-bar}中使用它的意思。在这种情况下,它将认为您想从foo中减去bar的值。该常见问题解答条目说明了如何处理这种情况。

首先,应该清楚这些只是语法上的问题,否则 FreeMarker 不会对变量名中使用的字符或它们的长度进行限制。

如果特殊字符是减号(-,UCS 0x2D)或点(.,UCS 0x2E)或冒号(:,UCS 0x3A)之一,那么您要做的就是在这些字符前加上反斜杠(\),例如在foo\-bar中(自 FreeMarker 2.3.22 起)。这样 FreeMarker 就会知道您不是用相同的符号表示运算符。这适用于您指定未加引号的标识符的任何地方,例如宏和函数名,参数名以及所有类型的变量引用。 (请注意,这些转义仅适用于标识符,不适用于字符串 Literals.)

当特殊字符不是减号,点或冒号之一时,它将变得更加棘手。假设有问题的变量名称为“ a b”。然后:

  • 如果要读取变量:如果它是某物的子变量,则可以编写something["a+b"](请记住,something.x等效于something["x"])。如果是顶级变量,则可以通过特殊的哈希变量.vars访问这些变量,因此可以自然地,此技巧也适用于宏和函数调用:<@.vars["a+b"]/>.vars["a+b"](1, 2)

  • 如果要创建或修改变量:所有用于创建或修改变量的指令(例如assignlocalglobalmacrofunction等)均允许引用目标变量名称。例如,<#assign foo = 1><#assign "foo" = 1>相同。因此,您可以编写<#assign "a+b" = 1><#macro "a+b">之类的东西。

  • 不幸的是,您不能使用这样的变量名称(包含-.:以外的特殊字符)作为宏参数名称。

  • 12.当我尝试使用* X * JSP 自定义标记时,为什么会出现“ java.lang.IllegalArgumentException:参数类型不匹配”的问题?

    • 首先,请更新 FreeMarker,因为 2.3.22 及更高版本给出了更有用的错误消息,几乎可以回答该问题。无论如何,原因如下。在 JSP 页面上,您引用了所有参数(属性)值,如果参数的类型是字符串,布尔值或数字,则它并不重要。但是由于自定义标记可以作为普通的用户定义的 FTL 指令在 FTL 模板中访问,因此您必须在自定义标记中使用 FTL 语法规则,而不是 JSP 规则。因此,根据 FTL 规则,您不得引用布尔值和数值参数值,否则它们将被解释为字符串值,并且当 FreeMarker 尝试将值传递给需要非字符串值的自定义标签时,这将导致类型不匹配错误。 。

例如,Struts Tiles insert标签的flush参数为布尔值。在 JSP 中,正确的语法是:

<tiles:insert page="/layout.jsp" flush="true"/>
...

但在 FTL 中,您应该写:

<@tiles.insert page="/layout.ftl" flush=true/>
...

同样,出于类似原因,这是错误的:

<tiles:insert page="/layout.jsp" flush="${needFlushing}"/>
...

并且您应该写:

<tiles:insert page="/layout.jsp" flush=needFlushing/>
...

(不是flush=${needFlushing}!)

  • 13.如何像jsp:include那样包含其他资源?
    • 不适用于<#include ...>,因为它仅包含另一个 FreeMarker 模板,而没有涉及 Servlet 容器。

由于您要寻找的包含方法与 Servlet 相关,并且纯 FreeMarker 不了解 Servlet 甚至 HTTP,因此由 Web 应用程序框架决定是否可以执行此操作以及如何执行。例如,在 Struts 2 中,您可以这样做:

<@s.include value="/WEB-INF/just-an-example.jspf" />

如果 Web 应用程序框架的 FreeMarker 支持基于freemarker.ext.servlet.FreemarkerServlet,那么您也可以这样做(因为 FreeMarker 2.3.15 起):

<@include_page path="/WEB-INF/just-an-example.jspf" />

但是如果 Web 应用程序框架提供了自己的解决方案,那么您可能更喜欢这样做,毕竟它可以做一些特别的事情。

有关include_page read this...的更多信息

  • 14.如何将参数作为纯java.lang.*/java.util.*对象获取到我的 Plain-Java 方法/ TemplateMethodModelEx/TemplateTransformModel/TemplateDirectiveModel实现中?
    • 不幸的是,没有针对此问题的简单通用解决方案。问题在于 FreeMarker 对象包装非常灵活,这在您从模板访问变量时很好,但是使 Java 方面的包装成为一个棘手的问题。例如,可以将非java.util.Map对象包装为TemplateHashModel(FTL 哈希变量)。但是,由于根本没有环绕的java.util.Map,因此无法将其解包到java.util.Map

那该怎么办呢?基本上有两种情况:

  • 出于演示目的而编写的指令和方法(例如用于帮助 FreeMarker 模板的“工具”种类)应将其参数声明为TemplateModel -s 以及更具体的子接口。毕竟,对象包装是关于将数据模型转换为符合表示层目的的东西,这些方法是表示层的一部分。如果在那里仍然需要普通的 Java 类型,则可以转到当前ObjectWrapperObjectWrapperAndUnwrapper接口(可以通过Environment.getObjectWrapper()获得)。

  • 不是用于表示相关任务的方法(而是用于业务逻辑等)的方法应实现为纯 Java 方法,并且根本不要使用任何 FreeMarker 特定的类,因为根据 MVC 范例,它们必须独立于表示技术(FreeMarker )。如果从模板中调用了这种方法,则object wrapper负责确保将参数转换为正确的类型。如果您使用DefaultObjectWrapperBeansWrapper,则会自动发生。对于DefaultObjectWrapper,如果您是将其不兼容改进设置为 2.3.22,则此机制会更好。

  • 15.为什么我不能在myMap[myKey]表达式中使用非字符串键?现在该怎么办?

    • FreeMarker 模板语言(FTL)的“哈希”类型与 Java 的Map不同。 FTL 的哈希也是一个关联数组,但它仅使用字符串键。这是因为它是为子变量引入的(如user.password中的password,与user["password"]相同),并且变量名是字符串。

如果只需要列出Map的键值对,则可以编写类似<#list myMap as k, v>${k}: ${v}</#list>的代码(有关这里的指令列表的更多信息)。枚举Map条目,并支持非字符串键。这需要 FreeMarker 2.3.25 或更高版本。 (如果由于某种原因无法升级到 2.3.25,则可以使用Map的 Java API,例如<#list myMap?api.entrySet() as kvp>${kvp.key}: ${kvp.value}</#list>.)

如果您需要做的不仅仅是列出,您将不得不使用Map的 Java API。您可以这样做:myMap?api.get(nonStringKey)。但是,要启用?api,可能需要对 FreeMarker 进行一些配置(在这里看到更多)。

请注意,由于 Java 的Map特定于键的确切类,因此至少对于模板内部计算出的数字键,您必须将其转换为正确的 Java 类型,否则将找不到该项目。例如,如果您在 Map 中使用Integer键,则应 Importing${myMap.get(numKey?int)}。这是因为 FTL 的故意简化类型系统只有一个数字类型,而 Java 区分了许多数字类型。请注意,当键值直接来自数据模型时(即,您没有在模板中通过算术运算修改它的值),包括当它是方法的返回值时,都不需要进行强制转换。在包装之前具有适当的类,因为展开的结果将是原始类型。

  • 16.当我使用?keys/?values列出 Map 的内容(哈希)时,我将java.util.Map方法与实际的 Map 项混合在一起。当然,我只想获取 Map 条目。

    • 当然,您使用的是纯BeansWrapper作为对象包装器(而不是默认的DefaultObjectWrapper)或其自定义子类,并且其simpleMapWrapper属性留给false。不幸的是,这是默认值BeansWrapper(为了向后兼容),因此必须在实例化时将其显式设置为true。另外,至少从 2.3.22 版本开始,应用程序应仅使用DefaultObjectWrapper(与其不兼容的改进设置为至少 2.3.22-如果从纯BeansWrapper切换则尤为重要),它永远不会出现此问题。
  • 17.如何在 FreeMarker 模板中修改序列(列表)和哈希(Map)?

    • 首先,您可能不想修改序列/哈希,而只需串联(添加)其中的两个或多个,这会导致新的序列/哈希,而不是修改现有的序列/哈希。在这种情况下,请使用sequence concatenation哈希级联运算符。另外,您可以使用subsequence operator而不是删除序列项。但是,请注意性能影响:这些操作很快,但是这些操作的许多后续应用(例如,当您将该操作的结果用作另一个操作的 Importing 时,以及等等)的阅读速度会很慢。

现在,如果您仍要修改序列/哈希,请 continue 阅读...

FreeMarkes 模板语言不支持序列/哈希的修改。它用于显示已经计算的内容,而不是用于计算数据。保持模板简单。但是不要放弃,您会在下面看到一些建议和技巧。

最好的办法是,您可以在数据模型构建器程序和模板之间划分工作,以使模板不需要修改序列/哈希。也许如果您重新考虑数据模型,您将意识到这是可能的。但是,很少有情况需要修改某些复杂但纯粹与表示相关的算法的序列/哈希。这种情况很少发生,因此请三思而后行,即该计算(或其一部分)是否属于数据模型域而不是表示域。假设您确定它属于演示文稿域。例如,您想以某种非常聪明的方式显示关键字索引,该关键字的算法需要您创建和编写一些序列变量。然后,您应该执行以下操作(丑陋的情况有丑陋的解决方案...):

<#assign caculatedResults =
    'com.example.foo.SmartKeywordIndexHelper'?new().calculate(keywords)>
<#-- some simple algorithms comes here, like: -->
<ul>
  <#list caculatedResults as kw>
    <li><a href="${kw.link}">${kw.word}</a>
  </#list>
</ul>

也就是说,您将表示任务的复杂部分从模板移到了 Java 代码中。请注意,它不会影响数据模型,因此表示仍然保持与其他应用程序逻辑的分离。当然,缺点是为此,模板作者将需要 Java 程序员的帮助,但是对于可能仍然需要的复杂算法。

现在,如果您仍然需要使用 FreeMarker 模板直接修改序列/哈希,则可以使用以下解决方案,但请阅读以下警告:

  • 您可以借助myMap?api.put(11, "eleven")内置的api来访问java.util.Map的 Java API。但是,您将需要从某个地方获取Map(像{}这样的 FTL 哈希 Literals 就不够用,因为它是只读的,也不支持api)。例如,您可以将 Java 方法或TemplateMethodModelEx暴露给返回new LinkeHashMap()的模板,因此可以执行<#assign myMap = utils.newLinkedHashMap()>

  • 您可以编写TemplateMethodModelExTemplateDirectiveModel实现,以修改某些类型的序列/哈希。只是某些类型,因为TemplateSequenceModelTemplateHashModel没有修改方法,因此您将需要序列或哈希来实现一些其他方法。在 FMPP 中可以看到此解决方案的示例。它允许您执行以下操作(pp存储 FMPP 为模板提供的服务):

<#assign a = pp.newWritableSequence()>
<@pp.add seq=a value="red" />

pp.add指令仅适用于使用pp.newWritableSequence()创建的序列。因此,例如,模板创建者无法以此来修改来自数据模型的序列。

  • 如果您使用自定义包装器,则序列可以具有某些方法/指令(因此您可以编写诸如<@myList.append foo />)。

但是请注意,这些解决方案存在一个问题:sequence concatenationsequence slice运算符(如seq[5..10])和?reverse不会复制原始序列,而是将其包装(出于效率考虑),因此,如果稍后更改原始序列,则生成的序列将更改(异常混叠效果)。 hash concatenation的结果存在相同的问题;它只是包装了两个哈希,因此,如果您修改了先前添加的哈希,则生成的哈希将发生神奇的变化。解决方法是,在完成上述有问题的操作之后,请确保您不会修改用作 Importing 的对象,或者使用上述两点所述解决方案提供的方法来创建结果的副本(例如,在 FMPP 中,您可以执行<#assign b = pp.newWritableSequence(a[5..10])><#assign c = pp.newWritableHash(hashA + hashB)>)。当然,这很容易遗漏...因此,再次尝试构建数据模型,这样您就无需修改集合或使用演示任务帮助器类,如先前所示。

    1. null和 FreeMarker 模板语言怎么样?
    • FreeMarker 模板语言根本不了解 Java 语言null。它没有null关键字,也无法测试某物是否为null。从技术上来说,它面对null时,会将其完全视为缺失变量。例如,如果x在数据模型中为null且根本不存在,则${x!'missing'}将显示“ missing”,您无法分辨出两者之间的区别。另外,例如,如果您要测试 Java 方法是否已返回null,只需编写类似<#if foo.bar()??>的内容。

您可能对此背后的理由感兴趣。从表示层的角度来看,null和不存在的事物几乎总是相同的。两者之间的区别通常只是技术细节,而是实现细节而不是应用程序逻辑的结果。您无法将某些内容与null进行比较(与 Java 不同);在模板中与null进行比较没有意义,因为模板语言不进行身份比较(如比较两个对象时的 Java ==运算符),但不进行身份比较(如 Java 的Object.equals(Object));不适用于null)。 FreeMarker 如何判断具体事物是否等同于缺失事物,因而未知事物?还是如果两个缺失(未知)的事物相等?当然,这些问题无法回答。

这种null -unware 方法至少存在一个问题。从模板调用 Java 方法时,您可能希望传递null值作为参数(因为该方法设计用于 Java 语言,其中null的概念是已知的)。在这种情况下,您可以利用 FreeMarker 的一个错误(在为将null值传递给方法提供正确的解决方案之前,我们将无法修复该错误):如果您将缺少的变量指定为参数,则不会导致错误, null将被传递给该方法。像foo.bar(nullArg)一样,将以null作为参数调用bar方法,假设不存在名称为“ nullArg”的变量。

  • 19.如何在表达式中使用指令(宏)的输出(作为另一个指令的参数)?
    • 使用assignlocal指令将输出捕获到变量中。例如:
<#assign capturedOutput><@outputSomething /></#assign>
<@otherDirective someParam=capturedOutput />
  • 20.为什么在输出中有“?”-s 而不是字符* X *?
    • 这是因为您要打印的字符无法用用于输出流的charset(编码)来表示,因此 Java 平台(不是 FreeMarker)用问号代替了有问题的字符。通常,您应该对输出使用与模板相同的字符集(使用模板对象的getEncoding()方法),或者甚至更安全,您应始终对输出使用 UTF-8 字符集。用于输出流的字符集不是由 FreeMarker 决定的,而是由您决定的,当您创建Writer并传递给模板的process方法时。

示例:在这里我在 servlet 中使用 UTF-8 字符集:

...
resp.setContentType("text/html; charset=utf-8");
Writer out = resp.getWriter();
...
t.process(root, out);
...

请注意,问号(或其他替换字符)可能在 FreeMarker 外部生成,在这种情况下,上述方法显然无济于事。例如,错误/未配置的数据库连接或 JDBC 驱动程序可能已经在文本中带有替换字符。 HTML 表单是编码问题的另一个潜在来源。最好在各个位置打印字符串字符的数字代码,以查看首先出现问题的位置。

您可以阅读有关字符集和 FreeMarker here...的更多信息。

  • 21.模板执行完成后如何检索模板中计算出的值?
    • 首先,请确保您的应用程序设计良好:模板应该显示数据,并且几乎从不计算数据。如果您仍然确定要这样做,请 continue 阅读...

使用<#assign x = "foo">时,实际上并不会修改数据模型(因为它是只读的,因此请参见:Programmer's Guide/Miscellaneous/Multithreading),而是在处理的运行时environment中创建x变量(请参见程序员指南/其他/变量,范围)。问题在于,Template.process返回时将丢弃此运行时环境,因为它是为单个Template.process调用创建的:

// internally an Environment will be created, and then discarded
myTemplate.process(root, out);

为避免这种情况,您可以执行与上面相同的操作,只是您有机会返回模板中创建的变量:

Environment env = myTemplate.createProcessingEnvironment(root, out);
env.process();  // process the template
TemplateModel x = env.getVariable("x");  // get variable x
  • 22.如何为动态构造的变量名分配(或#import)(就像存储在另一个变量中的名称一样)?
    • 如果您确实无法避免这样做(您应该这样做,这很令人困惑),则可以通过在字符串中动态构建适当的 FTL 源代码,然后使用interpret built-in来解决此问题。例如,如果要分配名称存储在varName变量中的变量:
<@"<#assign ${varName}='example'>"?interpret />
  • 23.我可以允许用户上传模板吗?这对安全有何影响?

    • 通常,除非这些用户是应用程序开发人员,系统 Management 员或其他高度信任的人员,否则您不应该允许这样做。就像*.java文件一样,将模板视为源代码的一部分。如果您仍然希望允许不受信任的用户上传模板,请考虑以下事项:
  • 数据模型和包装(Configuration.setObjectWrapper):数据模型可以访问已放入数据模型中的某些对象的公共 Java API。默认情况下,对于不是特殊处理类型的对象(StringNumberBooleanDateMapList,数组和其他一些类型)的对象,将公开其公共 Java API,包括继承的大多数方法来自标准 Java 类(getClass()等)。为了避免这种情况,您必须构造数据模型,以便仅公开模板 true 需要的成员。一种可能性是使用SimpleObjectWrapper(通过Configuration.setObjectWrapperobject_wrapper设置),然后仅从Map -s,List -s,Array -s,String -s,Number -s,Boolean -s 和Date -s 创建数据模型。但是对于许多应用程序来说,它们的限制过于严格,而必须创建一个WhitelistMemberAccessPolicy,并创建一个使用它的DefaultObjectWrapper(或您将使用的其他BeansWrapper子类)。有关更多信息,请参见WhitelistMemberAccessPolicy的 Java API 文档。 (或者,您当然可以滚动自己的MemberAccessPolicy实现,甚至可以滚动自己的限制性ObjectWrapper实现.)

始终希望模板可能会得到一些您自己尚未放入数据模型的对象。值得注意的是,模板始终可以使用.locale_object表达式获取Locale对象。或者您正在使用的 Web 应用程序框架可能会公开一些对象,例如 Servlet 作用域中的属性。此类对象仍将使用在Configuration中设置的ObjectWrapper包裹,这就是为什么确保该级别的安全性很重要的原因。控制模板可以访问哪些对象很困难,但是您可以集中控制它们可以访问的任何对象的成员。

如果您要在自定义代码中创建TemplateModel -s(而不是由ObjectWrapper创建),请确保避免使用new SimpleSequence()之类的已弃用容器构造函数,因为它们将使用也已弃用的默认对象包装器实例,该实例没有相同的限制而不是您在Configuration上设置的ObjectWrapper

另外,不要忘记?api built-in,如果您已启用它(默认情况下处于禁用状态)。尽管大多数ObjectWrapper -s 不会直接公开Map -s,List -s 和类似容器状对象的 Java API,所以someMap.someJavaMethod()无法使用,但是使用?api模板作者仍然可以使用?api的 Java API。这些对象,例如someMap?api.someJavaMethod()。但是请注意ObjectWrapper仍在控制之中,因为它决定了哪些对象支持?api,并且?api将为它们提供什么(通常与通用 POJO 相同)。 ?api也不会显示MemberAccessPolicy不允许的成员(假设您使用的ObjectWrapper行为良好,例如DefaultObjectWrapper)。

如果您使用的是默认的对象包装器类(freemarker.template.DefaultObjectWrapper)或它的子类,则应通过重写wrapDomNode(Object obj)来禁用它的 XML(DOM)包装功能,以便它可以这样做:return getModelFactory(obj.getClass()).create(obj, this);。 XML 包装功能的问题在于,它以特殊的方式包装org.w3c.dom.Node对象以使其更易于在模板中使用,该功能通过设计使模板作者可以评估任意 XPath 表达式,而 XPath 在某些设置中可以做得太多。如果确实需要 XML 包装工具,请仔细检查设置中可能使用的 XPath 表达式。另外,请确保不要使用已久且不推荐使用的更危险的freemarker.ext.xml软件包,而仅使用freemarker.ext.dom。另外,请注意,使用 XML 包装功能时,不允许MemberAccessPolicy中的org.w3c.dom.Node方法无效,因为它不会直接将 Java Node成员公开给模板。

最后但并非最不重要的一点是,有些人可能意识到标准对象包装器过滤掉了一些众所周知的“不安全”方法(如System.exit)的历史遗留。永远不要依赖它,因为它只会阻止较小的 sched 义列表中的方法。标准的 Java API 庞大且不断增长,然后有第三方库和您自己的应用程序的 API。显然,不可能将所有有问题的成员列入黑名单。

  • 模板加载器(Configuration.setTemplateLoader):模板可以按名称(按路径)加载其他模板,例如<#include "../secret.txt">。为了避免加载敏感数据,您必须使用TemplateLoader仔细检查要加载的文件是否应该公开。 FreeMarker 尝试阻止模板根目录之外的文件加载,无论模板加载器如何,但根据底层存储机制,FreeMarker 可能会考虑无法利用的漏洞(例如,~跳转到当前用户的家中)目录)。注意freemarker.cache.FileTemplateLoader检查规范路径,因此这可能是此任务的不错选择,但是,添加文件 extensions 检查(文件必须为*.ftl)可能是个好主意。

  • 内置的new(Configuration.setNewBuiltinClassResolverEnvironment.setNewBuiltinClassResolver):用于"com.example.SomeClass"?new()之类的模板,对于部分用 Java 实现的 FTL 库很重要,但普通模板则不需要。 new不会实例化非TemplateModel -s 的类,而 FreeMarker 包含TemplateModel类,该类可用于创建任意 Java 对象。其他“危险” TemplateModel -s 可能存在于您的 Classpath 中。另外,即使一个类未实现TemplateModel,其静态初始化也将运行。为避免这些情况,应使用TemplateClassResolver,将可访问类限制为绝对最小值(可能基于哪个模板要求它们),例如TemplateClassResolver.ALLOWS_NOTHING_RESOLVER不要使用TemplateClassResolver.SAFER_RESOLVER,它对于此目的的限制还不够!请注意,并且仅当ObjectWrapperBeansWrapper或其子类(通常是DefaultObjectWrapper)时,?new才不能访问MemberAccessPolicy不允许的构造函数。

  • 拒绝服务(DoS)攻击:创建可以永久永久运行(带有循环)或耗尽内存(通过串联成字符串)的模板很简单。 FreeMarker 无法强制执行 CPU 或内存使用限制,因此 FreeMarker 级别上没有解决方案。

  • 24.如何用 Java 语言而不是模板语言实现函数或宏?

    • 尚不可能(但是),但是如果您分别编写实现freemarker.template.TemplateMethodModelExfreemarker.template.TemplateDirectiveModel的类,然后在编写<#function my ...>...</#function><#macro my ...>...</#macro>的地方写<#assign my = "your.package.YourClass "? new ()>的类,则可能会非常相似。请注意,为此使用assign指令是可行的,因为函数(和方法)和宏只是 FreeMarker 中的普通变量。 (出于同样的原因,您也可以在调用模板之前将TemplateMethodModelExTemplateDirectiveModel实例放入数据模型,或者在初始化应用程序时将它们放入共享变量 Map(请参见freemarker.template.Configuration.setSharedVariable(String, TemplateModel))。)
    1. 在基于 Servlet 的应用程序中,当模板处理期间发生错误时,如何显示漂亮的错误页面而不是堆栈跟踪?
    • 首先,使用RETHROW_HANDLER而不是默认的DEBUG_HANDLER(有关模板异常处理程序read this...的更多信息)。现在,当发生错误时,FreeMarker 不会在输出中打印任何内容,因此控件就在您手中。捕获Template.process(...)的异常后,基本上可以采取两种策略:
  • 致电httpResp.isCommitted(),如果返回false,则致电httpResp.reset()并为访问者打印一个“不错的错误页面”。如果返回值为true,则尝试完成页面的打印操作,以使访问者清楚地知道由于 Web 服务器上的错误而导致页面生成突然中断。您可能需要打印许多多余的 HTML 结束标签,并设置颜色和字体大小,以确保错误消息在浏览器窗口中可以实际读取(请查看src\freemarker\template\TemplateException.javaHTML_DEBUG_HANDLER的源代码以查看示例)。

  • 使用全页缓冲。这意味着Writer不会将输出逐步发送到 Client 端,而是将整个页面缓冲在内存中。由于您为Template.process(...)方法提供了Writer实例,因此这是您的责任,FreeMarker 与它无关。例如,您可以使用StringWriter,如果Template.process(...)通过抛出异常返回,则忽略StringWriter所累积的内容,而是发送错误页面,否则将StringWriter的内容打印到输出中。使用此方法,您当然不必处理部分发送的页面,但是根据页面的特性,它可能会对性能产生负面影响(例如,对于缓慢生成的长页面,用户会遇到更多的响应延迟,服务器也会消耗更多的 RAM)。请注意,使用StringWriter肯定不是最有效的解决方案,因为随着累积内容的增长,它经常重新分配其缓冲区。

  • 26.我正在使用可视化 HTML 编辑器来处理模板标签。您会更改模板语言语法以适应我的编辑器吗?

    • 我们不会更改标准版本,因为很多模板都依赖它。

我们认为破坏模板代码的编辑器本身就被破坏了。优秀的编辑者应该忽略而不是不理解自己不了解的内容。

您可能对从 FreeMarker 2.3.4 开始可以使用[]而不是<>感兴趣。有关更多详细信息,read this...