FAQ

FreeMarker Pros:

<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>

FreeMarker Cons:

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

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

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

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

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,因为根据区域设置,小数点分隔符也可能会引起注意。

cfg.setLocale(java.util.Locale.ITALY);
// where cfg is a freemarker.template.Configuration object

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

font-size: ${fontSize?c}pt;

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

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

解决问题的提示:

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

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

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

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

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

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

例如,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}!)

由于您要寻找的包含方法与 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...的更多信息

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

如果只需要列出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 区分了许多数字类型。请注意,当键值直接来自数据模型时(即,您没有在模板中通过算术运算修改它的值),包括当它是方法的返回值时,都不需要进行强制转换。在包装之前具有适当的类,因为展开的结果将是原始类型。

现在,如果您仍要修改序列/哈希,请 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 模板直接修改序列/哈希,则可以使用以下解决方案,但请阅读以下警告:

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

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

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

您可能对此背后的理由感兴趣。从表示层的角度来看,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”的变量。

<#assign capturedOutput><@outputSomething /></#assign>
<@otherDirective someParam=capturedOutput />

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

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

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

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

使用<#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
<@"<#assign ${varName}='example'>"?interpret />

始终希望模板可能会得到一些您自己尚未放入数据模型的对象。值得注意的是,模板始终可以使用.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。显然,不可能将所有有问题的成员列入黑名单。

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

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

上一章 首页 下一章