FreeMarker与JSP 2.0 + JSTL组合进行比较。
FreeMarker优点:
FreeMarker不受Servlet或网络/ Web的限制; 它只是一个类库通过将模板与Java对象(数据模型)合并来生成文本输出。您可以随时随地执行模板; 没有HTTP请求转发或类似的技巧,根本不需要Servlet环境。因此,您可以轻松地将其集成到任何系统中。
更简洁的语法 考虑这个JSP(假设 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
c:if test="${t}">
True
Do this
Do that
- ${i}
和等效的FTL:
<#if t>
True
#if>
<#if n == 123>
Do this
<#else>
Do that
#if>
<#list ls as i>
- ${i}
#list>
在模板中没有servlet特定的范围和其他高度技术性的东西(除非你有意将它们暴露在数据模型中)。它是从一开始就为MVC制作的,它只关注演示。
您可以从任何地方加载模板; 从类路径,数据库等
缺省情况下,区域设置敏感数字和日期格式。当您为人类观众输出时,您需要做的只是写作,${x}
而不是
。
更容易定义特殊的宏和功能。
在地毯下没有清扫错误。缺少变量和null
-s不会默认默认为 0
/ false
/ empty-string,但会导致错误。在这里查看更多信息...
“对象包装”。这使您可以以定制的面向表示的方式将对象显示给模板(例如,请参阅此处使用此技术的模板可以看到W3C DOM节点)。
宏和函数只是变量,所以它们可以像任何其他值一样简单地作为参数值传递,放入数据模型等。
第一次(或更改后)访问页面时几乎不明显的延迟,因为没有昂贵的编译发生。
FreeMarker缺点:
不是“标准”。工具和IDE集成较少,开发人员知之甚少,总体来说,行业支持少得多。(但是,大多数JSP标记库可以在FreeMarker模板中使用正确的设置,除非它们基于.tag
文件。)
它的语法不符合HTML / XML规则,除了一些视觉相似性,这对于新用户来说是混乱的(这是简洁的代价)。JSP也不会跟随它,但它更接近它。
由于宏和函数只是变量,因此只能在运行时检测到不正确的指令和参数名称以及缺少的所需参数。
不适用于JSF。(它可以在技术上工作,但没有人实现了。)
如果您正在考虑在现有应用程序或仅支持JSP的遗留框架中使用FreeMarker替换JSP,则可以阅读此文件:程序员指南/其他/使用FreeMarker与servlet /使用FreeMarker for“Model 2”
2.为什么FreeMarker对于null
-s和缺失变量这么挑剔,怎么办?
要概述此条目是什么:FreeMarker默认情况下会尝试访问不存在的变量或 null
值(这两个与FreeMarker相同)作为错误,它会中止模板执行。
首先,你应该明白挑剔的原因。大多数脚本语言和模板语言相当宽容,缺少变量(和 null
-s),它们通常将它们视为空字符串和/或0和/或逻辑值。这个行为有几个问题:
它可能隐藏意外的错误,例如变量名称中的错字,或者模板作者引用程序员不会将该模板放入数据模型的变量,或程序员使用不同的名称时。人类很容易犯这样的错误,而电脑却没有,所以错过这个机会,模板引擎可以显示这些错误是一个糟糕的事情。即使您在开发过程中仔细检查模板的输出,也很容易查看错误 ,因为您会默认不会打印警告,因为您输入了变量名称(您注意到了吗?)。还要考虑维护,当你稍后修改你的应用程序; 大概你不会重新检查模板(许多应用程序有数百它们),每次仔细,所有可能的情况。单元测试通常不包括网页内容非常好(如果你有...); 他们大多只是检查网页中某些手动设置的模式,所以他们经常会使用实际上是错误的变化。但是如果页面出现异常,这是人类测试人员会注意到的,单元测试会注意到(整个页面都会失败),在生产中,维护者会注意到(假设有人检查错误日志)。当你以后修改你的应用程序; 大概你不会重新检查模板(许多应用程序有数百它们),每次仔细,所有可能的情况。单元测试通常不包括网页内容非常好(如果你有...); 他们大多只是检查网页中某些手动设置的模式,所以他们经常会使用实际上是错误的变化。但是如果页面出现异常,这是人类测试人员会注意到的,单元测试会注意到(整个页面都会失败),在生产中,维护者会注意到(假设有人检查错误日志)。当你以后修改你的应用程序; 大概你不会重新检查模板(许多应用程序有数百它们),每次仔细,所有可能的情况。单元测试通常不包括网页内容非常好(如果你有...); 他们大多只是检查网页中某些手动设置的模式,所以他们经常会使用实际上是错误的变化。但是如果页面出现异常,这是人类测试人员会注意到的,单元测试会注意到(整个页面都会失败),在生产中,维护者会注意到(假设有人检查错误日志)。大概你不会重新检查模板(许多应用程序有数百它们),每次仔细,所有可能的情况。单元测试通常不包括网页内容非常好(如果你有...); 他们大多只是检查网页中某些手动设置的模式,所以他们经常会使用实际上是错误的变化。但是如果页面出现异常,这是人类测试人员会注意到的,单元测试会注意到(整个页面都会失败),在生产中,维护者会注意到(假设有人检查错误日志)。大概你不会重新检查模板(许多应用程序有数百它们),每次仔细,所有可能的情况。单元测试通常不包括网页内容非常好(如果你有...); 他们大多只是检查网页中某些手动设置的模式,所以他们经常会使用实际上是错误的变化。但是如果页面出现异常,这是人类测试人员会注意到的,单元测试会注意到(整个页面都会失败),在生产中,维护者会注意到(假设有人检查错误日志)。覆盖网页内容非常好(如果你有...); 他们大多只是检查网页中某些手动设置的模式,所以他们经常会使用实际上是错误的变化。但是如果页面出现异常,这是人类测试人员会注意到的,单元测试会注意到(整个页面都会失败),在生产中,维护者会注意到(假设有人检查错误日志)。覆盖网页内容非常好(如果你有...); 他们大多只是检查网页中某些手动设置的模式,所以他们经常会使用实际上是错误的变化。但是如果页面出现异常,这是人类测试人员会注意到的,单元测试会注意到(整个页面都会失败),在生产中,维护者会注意到(假设有人检查错误日志)。<#if hasWarnigs>print warnings here...#if>
做出危险的假设。脚本语言或模板引擎对应用程序领域一无所知,所以当它确定不知道为0 / false的东西的价值时,这是一个相当不负责任和任意的事情。只是因为不知道你现在的银行余额是多少,我们可以说是0美元?只是因为不知道患者是否患有青霉素过敏,我们只能说他/她没有吗?只要考虑这些错误的含义。显示错误页面通常比显示不正确的信息更好,导致用户端的错误决定。
在这种情况下,不挑剔的是大部分地毯(不面对问题),当然大多数人觉得更方便,但是我们相信在大多数情况下,严格的会节省您的时间并提高您的软件质量长跑。
另一方面,我们认识到,有些情况下,您不希望FreeMarker成为好的理由,因此有解决方案:
您的数据模型包含null
-s或具有可选变量通常是正常的 。在这种情况下使用这些操作符。如果您使用它们太频繁,请尝试重新考虑您的数据模型,因为依赖它们太多不会使模板太冗长,但会增加隐藏错误和打印任意错误输出的可能性(由于上述原因) 。
在某些应用程序中,您可能希望显示不完整/损坏的页面,而不是错误页面。在这种情况下,您可以使用另一个错误处理程序。自定义错误处理程序可以跳过有问题的部分,或者在其中显示错误指示器,而不是中止整个页面呈现。但是请注意,虽然错误处理程序不会给变量提供任意的默认值,但是对于显示关键信息的页面来说,可能会更好地显示错误页面。
如果页面包含不是非常重要的(像一些边条)的部分,另一种功能,可能感兴趣的是在attempt
/ recover
指令。
FreeMarker使用Java平台的区域设置敏感数字格式化功能。您的区域设置的默认数字格式可能会使用分组或其他格式。如果您不想要,则必须使用number_format
FreeMarker设置覆盖Java平台建议的数字格式。例如:
cfg.setNumberFormat( “0 ######。”); //现在它将打印1000000 //其中cfg是freemarker.template.Configuration对象
但是请注意,人们经常发现难以读取大数字而不分组分隔符。因此,一般来说,建议保留它们,如果数字是“计算机受众”(在分组分隔符上混淆)的情况下,请使用c
内置的。例如:
对于计算机用户,您需要?c
反正,因为十进制分隔符也可以根据区域设置警惕。
不同的国家使用不同的十进制/分组分隔符号。如果您看到不正确的符号,那么可能您的区域设置未正确设置。设置JVM的默认语言环境,或使用locale
FreeMarker设置覆盖默认语言环境。例如:
cfg.setLocale(java.util.Locale.ITALY); //其中cfg是freemarker.template.Configuration对象
但是,有时你想输出一个不是人类观众的数字,而对于“电脑观众”(像你想在CSS中打印一个大小),在这种情况下,你必须使用点作为小数分隔符,而不管语言环境(语言)的页面。为了使用c
内置的,例如:
font-size:$ { fontSize?c } pt;
${aBoolean}
,以及如何解决?
与数字不同,布尔没有普遍接受的格式,甚至不是同一页面中的通用格式。喜欢当您在HTML页面上显示某个产品是否可以洗涤时,您几乎不希望为访问者显示“Washable:true”,而是“Washable:yes”。所以我们强迫模板作者(通过${washable}
导致错误)找出他的人类知识如何在给定的地方显示布尔值。格式化一个布尔就像常见的方式${washable?string("yes", "no")}
,${caching?string("Enabled", "Disabled")}
,${heating?string("on", "off")}
,等。
但是,有两种情况是不切实际的:
当打印布尔值以生成计算机语言输出,因此您想要 true
/ false
,使用 。(这至少需要FreeMarker 2.3.20。在此之前,通常的做法是写入 ,但是这是非常危险的,因为它的输出取决于当前的布尔格式设置,默认为 / 。)${someBoolean?c}
${someBoolean?string}
"true"
"false"
当您以相同的方式格式化大多数布尔值。在这种情况下,您可以设置boolean_format
setting(Configuration.setBooleanFormat
)来反映,然后自从FreeMarker 2.3.20就可以写 。(注意,这不适用于 / 虽然 - 你必须在那里使用。)${someBoolean}
true
false
?c
TemplateNotFoundException
或者
FileNotFoundException
,“Template not found”错误信息)
首先,您应该知道FreeMarker不直接从文件系统路径加载模板。相反,它使用一个简单的虚拟文件系统,可以读取非文件系统资源(jar内部的模板,数据库表内的模板等)。该虚拟文件由配置设置决定Configuration.setTemplateLoader(TemplateLoader)
。即使TemplateLoader
您正在使用地图到文件系统,它将有一个基本目录,其中包含所有的模板,这将是虚拟文件系统的根目录,您无法访问(即绝对路径将是仍然相对于虚拟文件系统根)。
提示解决问题:
如果您是配置FreeMarker的用户,请确保设置正确 TemplateLoader
。
否则看看模板未找到错误的消息是否包含所TemplateLoader
使用的描述 。如果没有,您使用的是旧的FreeMarker版本,因此更新它。获取 FileNotFoundException
,而不是TemplateNotFoundException
也是一个迹象,所以你会得到错误信息帮助较小。(如果TemplateLoader
在错误消息中 foo.SomeTemplateLoader@64f6106c
,因此没有显示一些相关参数,您可以要求作者定义更好 toString()
。)
一个常见的错误是使用一个 FileTemplateLoader
基于Servlet的Web应用程序,而不是一个WebappTemplateLoader
。它可能在一个环境中工作,但不能在另一个环境中工作,因为Servlet规范不会对您的资源作为普通文件提供许可,即使war
提取文件也不会。
知道当您从另一个模板中包含/导入模板时,如果您没有启动模板名称/
,它将相对于包含模板的目录进行解释。错误消息包含完整(已解析)的名称,因此您应该注意到这一点。
检查您是否使用\
(反斜杠)而不是/
(斜杠)。(FreeMarker 2.3.22及更高版本将在错误消息中提醒您。)
作为最后的手段,打开类别的调试级别日志记录(在您正在使用的日志框架中)freemarker.cache
,以查看更多的发生。
您确定使用为实际使用的FreeMarker版本编写的文档吗?特别注意,我们的在线文档是针对最新稳定的FreeMarker版本。你可以使用一个较旧的版本; 更新它。
您确定Java类加载器找到与freemarker.jar
您期望使用的相同 吗?也许有一个旧版本的freemarker.jar
周围,阴影从来没有。要查看此项,请尝试使用模板打印版本号${.version}
。(如果它与“未知的内置变量:版本”错误消息死亡,那么您使用非常非常旧的版本。)
如果您怀疑问题是您有多个 freemarker.jar
-s,典型的弊端是某些模块具有与旧freemarker
组ID 相关的Maven或Ivy依赖关系 ,而不是更现代的org.freemarker
组ID。由于不同的组ID,这些不被Maven或Ivy视为冲突的工件,因此两个版本都会进入。在这种情况下,您必须排除freemarker
依赖关系。
如果您认为文档或FreeMarker出错,请使用错误跟踪器或邮件列表进行报告。谢谢!
8. FreeMarker标签<
和
>
我的编辑器或XML解析器混淆。该怎么办?
从FreeMarker的启动2.3.4你可以用 [
和]
,而不是 <
和>
。更多详情请阅读...
FreeMarker的有关于变量名中使用的字符,也不会就变量名的长度没有限制,但为方便起见尽量选择可以用简单的变量引用表达式中使用的变量名(看到这里)。如果你必须选择一个更加极端的变量名,这不是一个大问题:见这里。
10.如何使用包含减号(-
),冒号(
:
),点(
.
)或其他特殊字符的变量名称(宏名称,参数名称)?
如果你有一个名为“foo-bar”的变量,FreeMarker会误会你的意思${foo-bar}
。在这种情况下,它会相信你想减去bar
from 的值 foo
。这个FAQ条目解释了如何处理这样的情况。
首先应该清楚的是,这些只是语法问题,否则FreeMarker对变量名中使用的字符也没有限制,也没有限制它们的长度。
如果特殊字符是减号(-
UCS 0x2D)或点(.
,UCS 0x2E)或冒号(:
UCS 0x3A)之一,则所有您需要做的是\
在这些字符之前放置一个反斜杠(),就像在foo\-bar
(自从FreeMarker 2.3.22)。那么FreeMarker会知道你并不是用相同的符号表示运算符。这可以在您指定非引号标识符的地方,如宏和函数名称,参数名称以及所有类型的变量引用。(请注意,这些转义仅适用于标识符,而不是字符串文字。)
当特殊字符不是负号,点或冒号时,会变得更加棘手。假设有问题的变量名是“a + b”。然后:
如果你想读取变量:如果它是一个可变的东西,你可以写 something["a+b"]
(记住,something.x
等同于 something["x"])
。如果它是一个顶级变量,那些可以通过特殊的哈希变量来访问.vars
,所以你可以写 .vars["a+b"]
,自然,这一招可与宏观和函数调用太:<@.vars["a+b"]/>
, .vars["a+b"](1, 2)
。
如果你想创建或修改的变量:所有的指令,让您创建或修改的变量(如 assign
,local
,global
,macro
, function
,等),使目标变量名的报价。例如,<#assign foo = 1>
与...相同<#assign "foo" = 1>
。所以,你可以写东西喜欢 <#assign "a+b" = 1>
和<#macro "a+b">
。
不幸的是,你不能用这样的变量名(包含除其他特殊字符-
, .
和:
)作为宏参数名称。
所有的拳头,更新FreeMarker,因为2.3.22和更高版本给出了一个更有用的错误信息,这几乎可以解答这个问题。无论如何,原因如下。在JSP页面上,引用所有参数(属性)值,如果参数的类型是字符串或布尔值或数字,则不会这样做。但是,由于自定义标签可以在FTL模板中作为纯用户定义的FTL伪指令访问,因此必须在自定义标记中使用FTL语法规则,而不是JSP规则。因此,根据FTL规则,您不能引用布尔值和数值参数值,或者将它们解释为字符串值,
例如,flush
Struts Tiles insert
标签的参数是布尔值。在JSP中,正确的语法是:
flush =“true” /> ...
但在FTL你应该写:
<@ tiles.insert page =“/ layout.ftl” flush = true /> ...
另外,出于类似的原因,这是错误的:
flush =“$ {needFlushing}” /> ...
你应该写:
flush = needFlushing /> ...
(不flush=${needFlushing}
!)
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
...
TemplateMethodModelEx
/
TemplateTransformModel
/
TemplateDirectiveModel
实现的参数作为plain
java.lang.*
/
java.util.*
objects来获取?
不幸的是,这个问题没有简单的通用解决方案。问题是FreeMarker的对象包装非常灵活,当您从模板访问变量时,这是很好的,但是在Java方面展开一个棘手的问题。例如,可以将非java.util.Map
对象包装为 TemplateHashModel
(FTL哈希变量)。但是,它不能解开java.util.Map
,因为没有包裹java.util.Map
。
那么该怎么办呢?基本上有两种情况:
为演示目的而编写的指令和方法(如帮助FreeMarker模板的“工具”)应将其参数声明为TemplateModel
-s和更具体的子接口。毕竟,对象包装是关于将数据模型转换为用于表示层的目的的,这些方法是表示层的一部分。如果你仍然需要一个普通的Java类型,你可以转到ObjectWrapperAndUnwrapper
当前的 界面ObjectWrapper
(可以使用Environment.getObjectWrapper()
)。
不用于演示相关任务(但是对于业务逻辑等)的方法应该以纯Java方法实现,并且根本不应该使用任何FreeMarker特定的类,因为根据MVC范例,它们必须独立于演示技术(FreeMarker )。如果从模板调用这种方法,那么对象包装器有责任 确保将参数转换为适当的类型。如果你使用DefaultObjectWrapper
或BeansWrapper
那么这将会自动发生。因为DefaultObjectWrapper
,这种机制的效果要好得多,incompatibleImprovements
myMap[myKey]
表达式中使用非字符串键
?现在该怎么办?
FreeMarker模板语言(FTL)的“哈希”类型与Java不同Map
。FTL的散列也是一个关联数组,但是它也使用字符串键。这是因为它是为子变量引入的(如同password
, 与之user.password
相同user["password"]
),变量名称是字符串。
如果你只需要列出的该键值对 Map
,你可以写类似 <#list myMap as k, v>${k}: ${v}#list>
(见更多的list directive
在这里)。这将枚举Map
条目,并支持非字符串键。这需要FreeMarker 2.3.25或更高版本。(如果由于某种原因您无法升级到2.3.25,则可以使用相应的Java API Map
,如 <#list myMap?api.entrySet() as kvp>${kvp.key}: ${kvp.value}#list>
。)
如果您需要做的不仅仅是列表,那么您将不得不转而使用Java API Map
。你可以这样说:myMap?api.get(nonStringKey)
。但是, ?api
要启用,您可能需要配置FreeMarker一点(请参阅此处)。
请注意,由于Java Map
是关键字的确切类别,至少对于模板中计算的数字键,您必须将其转换为正确的Java类型,否则将不会找到该项。例如,如果您Integer
在地图中使用密钥,那么您应该写${myMap.get(numKey?int)}
。这是因为FTL的故意简化型系统只有一种数字类型,而Java区分了很多数值类型。请注意,当键值直接来自数据模型(即,您没有使用模板中的算术计算修改其值)时,不需要转换,包括方法返回值的情况,
?keys
/
?values
,我得到了
java.util.Map
与真实映射条目混合的方法。当然,我只想得到地图条目。
当然,你使用pure BeansWrapper
作为对象的包装器(而不是默认的 DefaultObjectWrapper
),或者它的一个自定义的子类,并且它的simpleMapWrapper
属性是剩下的false
。不幸的是,这是默认的BeansWrapper
(为了向后兼容),所以你必须明确地将它设置为 true
实例化的位置。此外,至少自2.3.22以来,应用程序应该只是使用 DefaultObjectWrapper
(其incompatibleImprovements
设置至少为2.3.22 - 如果您从纯粹转换BeansWrapper
,这一点尤为重要),这从来没有这个问题。
首先,您可能不想修改序列/散列,只需连接(添加)两个或更多的,这将导致新的序列/散列,而不是修改现有的序列/散列。在这种情况下,使用序列连接和散列连接运算符。此外,您可以使用子序列运算符,而不是删除序列项。但是,请注意性能影响:这些操作是快速的,但是这些操作的许多后续应用的结果的散列/序列(即,当您将操作的结果用作另一个操作的输入时,以及等等)会慢慢阅读。
现在,如果你仍然想修改序列/哈希值,然后阅读...
FreeMarkes模板语言不支持修改序列/散列。它用于显示已计算的东西,而不是用于计算数据。保持模板简单。但不要放弃,你会看到一些建议和窍门。
最好的是如果您可以在数据模型构建程序和模板之间划分工作,以使模板不需要修改序列/散列。也许如果你重新考虑你的数据模型,你会意识到这是可能的。但是,很少有一些情况需要修改序列/散列,以获得一些复杂而纯粹的表示相关算法。很少发生,所以考虑这个计算(或它的一部分)是否属于数据模型域而不是表示域。我们假设你确定它属于演示文稿领域。例如,您希望以非常聪明的方式显示关键字索引,其算法需要您创建和编写一些序列变量。
<#assign caculatedResults = 'com.example.foo.SmartKeywordIndexHelper'?新的()。计算(关键字)> <# - 这里有一些简单的算法,如: - >
也就是说,将演示任务的复杂部分从模板移出到Java代码中。请注意,它不影响数据模型,因此演示文稿仍然与其他应用程序逻辑分开。当然,缺点是模板作者需要Java程序员的帮助,但是对于可能需要的复杂算法,
现在,如果你仍然说你需要直接使用FreeMarker模板修改序列/哈希,这里有一些解决方案,但是请仔细阅读以下警告:
您可以使用内置 java.util.Map
的帮助 来访问Java API 。您将需要从某个地方获取(一个FTL哈希字面值不够,因为它只读,也不支持)。例如,您可以公开一个Java方法或 返回的模板,这样就可以 。api
myMap?api.put(11, "eleven")
Map
{}
api
TemplateMethodModelEx
new LinkeHashMap()
<#assign myMap = utils.newLinkedHashMap()>
您可以编写TemplateMethodModelEx
并TemplateDirectiveModel
可以修改某些类型的序列/散列的实现。只是某些类型,因为 TemplateSequenceModel
并 TemplateHashModel
没有修改的方法,所以你将需要序列或哈希来实现一些额外的方法。在FMPP中可以看到这个解决方案的一个例子。它允许你做这样的事情(pp
存储FMPP为模板提供的服务):
<#assign a = pp.newWritableSequence()> <@ pp.add seq = a value =“red”/>
该pp.add
指令仅与使用的序列一起使用 pp.newWritableSequence()
。因此,例如,模板作者无法使用此方式修改来自数据模型的序列。
如果您使用自定义的包装器(因此可以编写类似的东西<@myList.append foo />
),序列可以有一些方法/指令 。
但是要注意的是,这些解决方案有一个问题:序列连接,序列切片 操作符(如seq[5..10]
)并且 ?reverse
不复制原始序列,仅仅包装它(为了效率),所以如果原始序列稍后改变,则所得到的序列将改变异常混叠效应)。哈希连接的结果存在同样的问题; 它只是包装了两个散列,所以如果你修改了之前添加的哈希值,结果哈希将会神奇地改变。作为一种解决方法,在执行上述问题的操作后,要么确保不会修改用作输入的对象,<#assign b = pp.newWritableSequence(a[5..10])>
或创建结果的副本与由上述两个点(例如在FMPP你可以做描述的溶液提供了一种方法和 <#assign c = pp.newWritableHash(hashA + hashB)>
)。当然,这很容易错过,所以再次尝试构建数据模型,因此您不需要修改集合,或者使用前面所示的演示任务帮助器类。
null
和FreeMarker模板语言?
FreeMarker模板语言根本不知道Java语言null
。它没有 null
关键字,它不能测试是否有东西null
。当它在技术上面对a时 null
,它将其视为一个缺失的变量。例如,如果x
是 null
在数据模型中,如果它不存在,${x!'missing'}
将打印“丢失”,你不能说出差异。另外,如果你想要测试Java方法是否已经返回 null
,只需要写一些类似的东西 <#if foo.bar()??>
。
您可能对此背后的理由感兴趣。从表现层的观点来看,null
和不存在的东西几乎总是相同的。这两者之间的区别通常只是一个技术细节,而不是应用逻辑的实现细节的结果。你不能比较一些东西null
(不像Java); null
在模板中比较某些东西是没有意义的,因为模板语言不会进行身份比较(比如Java ==
比较两个对象时的Java 运算符),但是更常见的意义值比较(像Java的Object.equals(Object)
那样)与null
任何一个工作 )。FreeMarker如何告诉某些具体的内容是否与缺少的东西相等呢?或者如果两个丢失(未知)的东西是平等的?当然这些问题是无法回答的。
这种null
-unaware方法至少有一个问题 。当您从模板调用Java方法时,您可能希望将null
值作为参数传递 (因为该方法被设计为在Java语言中使用,其中的概念 null
已知)。在这种情况下,您可以利用FreeMarker的错误(我们不会修复,直到我们为null
方法传递值提供正确的解决方案):如果指定一个缺失的变量作为参数,那么它不会导致错误,但是null
将被传递给该方法。喜欢foo.bar(nullArg)
将调用bar
方法 null
作为参数,假设没有变量存在与“
使用assign
或local
指令将输出捕获到变量中 。例如:
<#assign takenOutput> <@ outputSomething /> #assign> <@otherDirective someParam = captureOutput />
这是因为您要打印的字符不能用输出流使用的字符集(编码)来表示,所以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表单是编码问题的另一个潜在来源。最好在各个地方打印字符串字符的数字代码,以查看问题出现在哪里。
您可以在这里阅读更多关于charsets和FreeMarker的信息
20.如何在模板执行完成后检索模板中计算的值?首先,确保您的应用程序设计良好:模板应显示数据,几乎不会计算数据。如果您仍然确定要这样做,请阅读...
当您使用时<#assign x = "foo">
,您实际上并没有修改数据模型(因为这是只读的,请参阅:程序员指南/杂项/多线程),但在处理x
的运行时环境中创建变量(参见Programmer's Guide / Miscellaneous /变量,范围)。问题是,当Template.process
返回时,这个运行时环境将被丢弃 ,因为它是为一次Template.process
调用创建的:
//内部将创建一个环境,然后丢弃 myTemplate.process(root,out);
为了防止这种情况,您可以执行以下操作,与上述相同,只不过您有机会返回模板中创建的变量:
环境env = myTemplate.createProcessingEnvironment(root,out); env.process(); //处理模板 TemplateModel x = env.getVariable(“x”); // get variable x
#import
转换)一个动态构造的变量名(比如将名称存储在另一个变量中)?
如果你真的不能避免这样做(你应该是混淆的),你可以通过在一个字符串中动态构建适当的FTL源代码,然后使用interpret
内置函数来解决这个问题。例如,如果要将其名称存储在varName
变量中的变量分配:
<@“<#assign $ {varName} ='example'>”?interpret />
一般来说,除非这些用户是系统管理员或其他受信任的人员,否则您不应该允许。考虑模板作为源代码的一部分,就像 *.java
文件一样。如果您仍然希望允许用户上传模板,请考虑以下几点:
拒绝服务(DoS)攻击:创建几乎永远运行(循环)或排出内存(通过连接到循环中的字符串)的模板是微不足道的。FreeMarker无法强制执行CPU或内存使用限制,因此这是FreeMarker级别没有解决方案。
数据模型和包装(Configuration.setObjectWrapper
):数据模型可以访问您放入数据模型的某些对象的公共Java API。默认情况下,不属于这帮专门处理类型(的实例的对象String
, Number
,Boolean
, Date
,Map
, List
,阵列,和其他几个人),其公开的Java API将被暴露。为了避免这种情况,您必须构建数据模型,以使其仅暴露模板所必需的内容。为了那个原因,SimpleObjectWrapper
您可能想使用(通过Configuration.setObjectWrapper
或 object_wrapper
设置),然后纯粹从Map
-s,List
-s,Array
-s, String
-s,Number
-s, Boolean
-s和Date
-s 创建数据模型。或者,您可以实现自己的极限制 ObjectWrapper
,例如可以安全地暴露您的POJO。
Template-loader(Configuration.setTemplateLoader
):模板可以按名称(通过路径)加载其他模板,如 <#include "../secret.txt">
。为了避免加载敏感数据,您必须使用TemplateLoader
双重检查文件加载是应该暴露的东西。FreeMarker尝试防止在模板根目录之外加载文件,而不管模板加载程序如何,但根据底层存储机制,FreeMarker无法考虑使用漏洞(例如, ~
跳转到当前用户的主目录)。请注意 freemarker.cache.FileTemplateLoader
检查规范路径,以便“ *.ftl
该new
内置(Configuration.setNewBuiltinClassResolver
,Environment.setNewBuiltinClassResolver
):它像模板中使用"com.example.SomeClass"?new()
,并且是针对在Java中部分地实现FTL库很重要,但不应该在正常的模板是必要的。虽然 new
不会实例化不是TemplateModel
-s的TemplateModel
类,FreeMarker包含一个 可以用于创建任意Java对象的类。其他“危险” TemplateModel
-s可以存在于您的类路径中。另外,即使一个类没有实现 TemplateModel
,它的静态初始化也将被运行。为了避免这些,TemplateClassResolver
TemplateClassResolver.ALLOWS_NOTHING_RESOLVER
这是不可能的(还),但非常类似的东西是可能的,如果你写一个实现类freemarker.template.TemplateMethodModelEx
或freemarker.template.TemplateDirectiveModel
分别,然后你在哪里写 或者 你写 吧。请注意,由于函数(和方法)和宏只是FreeMarker中的简单变量,因此使用此伪指令的指令。(出于同样的原因,你也可以把或 实例到数据模型调用模板之前,或进入共享变量地图(见 )当您初始化应用程序。)<#function my ...>...#function>
<#macro my ...>...#macro>
<#assign my = "your.package.YourClass "?
new
()>
assign
TemplateMethodModelEx
TemplateDirectiveModel
freemarker.template.Configuration.setSharedVariable(String, TemplateModel)
首先,使用RETHROW_HANDLER
而不是默认DEBUG_HANDLER
(有关模板异常处理程序的更多信息,请阅读...)。现在,当出现错误时,FreeMarker将不会向输出打印任何内容,因此控件在您的手中。在您捕获了 基本的异常之后, 您可以遵循两种策略:Template.process(...)
打电话httpResp.isCommitted()
,如果返回false
,那么你打电话给 httpResp.reset()
打印者打印一个“漂亮的错误页面”。如果返回值为 true
,则尝试完成页面打印,以使访问者清楚地看到页面生成由于Web服务器上的错误而突然中断。您可能需要打印大量冗余HTML终点标记的和设置颜色和字体大小,以确保该错误消息将在浏览器窗口中实际可读的(检查的源代码HTML_DEBUG_HANDLER
中 src\freemarker\template\TemplateException.java
看到的示例)。
使用全页缓冲。这意味着 Writer
不会将输出逐渐发送给客户端,而是将整个页面缓存在内存中。由于您提供的 方法Writer
实例 ,这是您的责任,FreeMarker与它无关。例如,您可以使用a ,如果 通过抛出异常返回,则忽略该内容,并发送错误页面,否则打印到输出的内容 。用这种方法你肯定不会 不得不处理部分发送的页面,但是它可能会对页面的特性造成负面的影响(例如,用户会慢慢生成长页面的响应延迟,服务器也将消耗更多的RAM )。请注意,使用a 肯定不是最有效的解决方案,因为它会随着累积内容的增长而重新分配缓冲区。Template.process(...)
StringWriter
Template.process(...)
StringWriter
StringWriter
StringWriter
我们不会更改标准版本,因为很多模板依赖于它。