FreeMarker
Apache FreeMarker是一个模板引擎,可用于从 HTML 到电子邮件等生成任何类型的文本输出。Spring 框架有内置集成,可用于将 Spring MVC 与 FreeMarker 模板一起使用。
View Configuration
以下示例展示了如何将 FreeMarker 配置为视图技术:
-
Java
-
Kotlin
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.freeMarker();
}
// Configure FreeMarker...
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/freemarker");
configurer.setDefaultCharset(StandardCharsets.UTF_8);
return configurer;
}
}
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.freeMarker()
}
// Configure FreeMarker...
@Bean
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
setTemplateLoaderPath("/WEB-INF/freemarker")
setDefaultCharset(StandardCharsets.UTF_8)
}
}
以下示例展示了如何在 XML 中配置同样的内容:
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:freemarker/>
</mvc:view-resolvers>
<!-- Configure FreeMarker... -->
<mvc:freemarker-configurer>
<mvc:template-loader-path location="/WEB-INF/freemarker"/>
</mvc:freemarker-configurer>
或者,你还可以声明 FreeMarkerConfigurer
bean 以完全控制所有属性,如以下示例所示:
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
<property name="defaultEncoding" value="UTF-8"/>
</bean>
你的模板需要存储在前面示例中所示的 FreeMarkerConfigurer
指定的目录中。根据前面的配置,如果你的控制器返回视图名称 welcome
,则解析器会查找 /WEB-INF/freemarker/welcome.ftl
模板。
FreeMarker Configuration
你可以通过设置 FreeMarkerConfigurer
bean 上的相应 bean 属性,将 FreeMarker “设置”和“共享变量”直接传递给 FreeMarker Configuration
对象(其由 Spring 管理)。freemarkerSettings
属性需要 java.util.Properties
对象,而 freemarkerVariables
属性需要 java.util.Map
。以下示例展示了如何使用 FreeMarkerConfigurer
:
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
<property name="freemarkerVariables">
<map>
<entry key="xml_escape" value-ref="fmXmlEscape"/>
</map>
</property>
</bean>
<bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/>
请参见 FreeMarker 文档,了解设置和变量的详细信息,因为它们适用于 Configuration
对象。
Form Handling
Spring 提供了一个标签库,用于 JSP,其中包括 <spring:bind/>
元素。此元素主要允许表单显示来自表单后备对象的数值,以及显示来自 Web 或业务层中的 Validator
的不成功验证的结果。Spring 在 FreeMarker 中也支持同样的功能,并提供其他方便的宏,用于生成表单输入元素本身。
The Bind Macros
标准宏集为在 spring-webmvc.jar
文件中为 FreeMarker 维护,以便在适当配置的应用程序中始终可用。
在 Spring 模板库中定义的一些宏被认为是内部(私有)的,但宏定义中不存在此类范围,使所有宏都对调用代码和用户模板可见。以下章节仅专注于你需要直接从模板内调用的宏。如果你希望直接查看宏代码,则该文件名为 spring.ftl
,位于 org.springframework.web.servlet.view.freemarker
包中。
Simple Binding
在基于将作为 Spring MVC 控制器的表单视图的 FreeMarker 模板的 HTML 表单中,你可以使用类似于以下示例的代码以类似于 JSP 等效项的方式将字段值绑定到显示错误消息以在每个输入字段中显示错误消息。以下示例显示了 personForm
视图:
<!-- FreeMarker macros have to be imported into a namespace.
We strongly recommend sticking to 'spring'. -->
<#import "/spring.ftl" as spring/>
<html>
...
<form action="" method="POST">
Name:
<@spring.bind "personForm.name"/>
<input type="text"
name="${spring.status.expression}"
value="${spring.status.value?html}"/><br />
<#list spring.status.errorMessages as error> <b>${error}</b> <br /> </#list>
<br />
...
<input type="submit" value="submit"/>
</form>
...
</html>
<@spring.bind>
需要一个“路径”参数,该参数包含命令对象的名称(它为“command”,但如果你在控制器配置中已更改,则除外),然后是句点和命令对象上你希望绑定的字段的名称。你还可以使用嵌套字段,例如 command.address.street
。bind
宏会假定在 web.xml
中由 ServletContext
参数 defaultHtmlEscape
指定的默认 HTML escape 行为。
称为 <@spring.bindEscaped>
的宏的另一种形式需要一个第二个参数,该参数明确指定在状态错误消息或值中是否应该使用 HTML escape。你可以根据需要将其设置为 true
或 false
。附加的表单处理宏简化了 HTML escape 的使用,并且你应尽可能使用这些宏。它们将在下一部分进行说明。
Input Macros
用于 FreeMarker 的附加便利宏可简化绑定和表单生成(包括验证错误显示)。使用这些宏来生成表单输入字段绝不是必需的,并且你可以将它们与简单的 HTML 或直接调用我们之前突出显示的 Spring 绑定宏相混合和匹配。
可用宏的以下表格显示了 FreeMarker Template (FTL) 定义以及每个宏需要的参数列表:
macro | FTL definition |
---|---|
|
<@spring.message code/> |
|
<@spring.messageText code, text/> |
|
<@spring.url relativeUrl/> |
|
<@spring.formInput path, attributes, fieldType/> |
|
<@spring.formHiddenInput path, attributes/> |
|
<@spring.formPasswordInput path, attributes/> |
|
<@spring.formTextarea path, attributes/> |
|
<@spring.formSingleSelect path, options, attributes/> |
|
<@spring.formMultiSelect path, options, attributes/> |
|
<@spring.formRadioButtons path, options separator, attributes/> |
|
<@spring.formCheckboxes path, options, separator, attributes/> |
|
<@spring.formCheckbox path, attributes/> |
|
<@spring.showErrors separator, classOrStyle/> |
在 FreeMarker 模板中, |
上述宏的所有参数都具有相一致的含义:
-
path
:要绑定的字段的名称(例如,“command.name”) -
options
:所有可从输入字段中选择的可用值数组的Map
。发送回表单并绑定到命令对象的键表示从表单中发送回的值。针对键存储的地图对象是对用户在表单中显示的标签,可能与表单发送回的相应值不同。通常,控制器将该映射作为参考数据提供。根据所需行为,可以使用任何Map
实现。对于严格排序的地图,可以使用SortedMap
(如TreeMap
),其带有合适的Comparator
;对于应当以插入顺序返回值的任意地图,使用LinkedHashMap
或commons-collections
中的LinkedMap
。 -
separator
:当多个选项可用作特殊元素(单选按钮或复选框)时,用于分离列表中每个选项的字符序列(如<br>
)。 -
attributes
:一个任意的标记或文本附加字符串,包含在 HTML 标记中。宏会照原文发送该字符串。例如,在textarea
字段中,可能提供属性(如“rows="5" cols="60"”)或传递样式信息,如 “style="border:1px solid silver"”。 -
classOrStyle
:对于showErrors
宏,包装每个错误的span
元素使用的 CSS 类的名称。如果未提供信息(或该值为空),错误包装在<b></b>
标记中。
以下部分概述了宏的示例。
Input Fields
formInput
宏使用 path
参数(command.name
)和附加的 attributes
参数(在即将出现的示例中为空)。该宏将与此所有其他表单生成宏一起对路径参数执行 Spring 隐式绑定。绑定保持有效,直到出现新的绑定,所以 showErrors
宏不需要再次传递路径参数——它在上次创建绑定的字段上执行操作。
showErrors
宏使用分隔符参数(用于分隔给定字段上的多个错误的字符),并且还接受第二个参数——这次是类名称或样式属性。请注意,FreeMarker 可以为属性参数指定默认值。以下示例显示了如何使用 formInput
和 showErrors
宏:
<@spring.formInput "command.name"/>
<@spring.showErrors "<br>"/>
以下示例显示了表单片段的输出,在字段中未提交任何值的情况下生成的名称字段并在提交表单后显示验证错误。验证通过 Spring 的 Validation 框架进行。
生成的 HTML 类似于以下示例:
Name:
<input type="text" name="name" value="">
<br>
<b>required</b>
<br>
<br>
formTextarea
宏的工作方式与 formInput
宏相同,并且接受相同参数列表。通常,第二个参数(attributes
)用于传递样式信息或 textarea
的 rows
和 cols
属性。
Selection Fields
你可以使用四个选择字段宏来生成 HTML 表单中的常见 UI 值选择输入:
-
formSingleSelect
-
formMultiSelect
-
formRadioButtons
-
formCheckboxes
这四个宏中的每个都接受一个选项的 Map
,其中包括表单字段的值和对应于该值的标签。值和标签可以相同。
下面的示例是 FTL 中的单选按钮。表单后备对象为此字段指定了 'London' 的默认值,所以不需要验证。呈现表单后,从城市列表中选择整个城市列表在模型中的名称 'cityMap' 下作为参考数据提供。以下清单显示了该示例:
...
Town:
<@spring.formRadioButtons "command.address.town", cityMap, ""/><br><br>
前面的清单呈现一行单选按钮,一个用于 cityMap
中的每个值,并使用分隔符 ""
。没有提供其他属性(宏的最后一个参数缺失)。cityMap
对地图中的每个键值对使用相同的 String
。地图的键是表单实际提交为 POST
请求参数的内容。地图值是用户看到的标签。在前一个示例中,给定一个众所周知的城市列表和表单后备对象中的一个默认值,HTML 类似于以下内容:
Town:
<input type="radio" name="address.town" value="London">London</input>
<input type="radio" name="address.town" value="Paris" checked="checked">Paris</input>
<input type="radio" name="address.town" value="New York">New York</input>
如果你的应用程序希望通过内部代码(例如)来处理城市,则可以创建带有适当键的代码地图,如下面的示例所示:
-
Java
-
Kotlin
protected Map<String, ?> referenceData(HttpServletRequest request) throws Exception {
Map<String, String> cityMap = new LinkedHashMap<>();
cityMap.put("LDN", "London");
cityMap.put("PRS", "Paris");
cityMap.put("NYC", "New York");
Map<String, Object> model = new HashMap<>();
model.put("cityMap", cityMap);
return model;
}
protected fun referenceData(request: HttpServletRequest): Map<String, *> {
val cityMap = linkedMapOf(
"LDN" to "London",
"PRS" to "Paris",
"NYC" to "New York"
)
return hashMapOf("cityMap" to cityMap)
}
代码现在生成的输出是收音机值相关的代码,但用户仍然看到更友好的城市名称,如下所示:
Town:
<input type="radio" name="address.town" value="LDN">London</input>
<input type="radio" name="address.town" value="PRS" checked="checked">Paris</input>
<input type="radio" name="address.town" value="NYC">New York</input>
HTML Escaping
前面描述的表单宏的默认用法会导致生成符合 HTML 4.01 的 HTML 元素,这些元素使用 Spring 的绑定支持所定义的 web.xml
文件中的 HTML 转义默认值。若要使元素符合 XHTML 或覆盖默认 HTML 转义值,您可以在模板中(或在模型中,您在其中可以看到模板)指定两个变量。将它们指定在模板中的优势在于,在模板处理的后面,它们可以被更改为不同的值,以对表单中不同的字段提供不同的行为。
若要对标签切换到 XHTML 合规,请为名为 xhtmlCompliant
的模型或上下文变量指定 true
值,如下例所示:
<#-- for FreeMarker -->
<#assign xhtmlCompliant = true>
在处理完此指令后,Spring 宏生成的任何元素现在都符合 XHTML。
以类似的方式,您可以指定每个字段的 HTML 转义,如下例所示:
<#-- until this point, default HTML escaping is used -->
<#assign htmlEscape = true>
<#-- next field will use HTML escaping -->
<@spring.formInput "command.name"/>
<#assign htmlEscape = false in spring>
<#-- all future fields will be bound with HTML escaping off -->