FreeMarker

Apache FreeMarker 是一个模板引擎,用于生成从 HTML 到电子邮件等各种文本输出。Spring Framework 内置了与 FreeMarker 模板结合使用 Spring MVC 的集成支持。

视图配置

以下示例展示了如何将 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 配置

您可以通过在 FreeMarkerConfigurer bean 上设置相应的 bean 属性,将 FreeMarker 的 'Settings' 和 'SharedVariables' 直接传递给 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"/>

有关设置和变量应用于 Configuration 对象的详细信息,请参阅 FreeMarker 文档。

表单处理

Spring 提供了一个用于 JSP 的标签库,其中包含 <spring:bind/> 元素等。此元素主要允许表单显示来自表单后备对象的值,并显示 Web 或业务层 Validator 验证失败的结果。Spring 还支持 FreeMarker 中的相同功能,并提供了用于生成表单输入元素本身的附加便利宏。

绑定宏

FreeMarker 的一组标准宏保留在 spring-webmvc.jar 文件中,因此对于适当配置的应用,它们始终可用。

Spring 模板库中定义的一些宏被视为内部(private),但宏定义中不存在这种作用域,因此所有宏都对调用代码和用户模板可见。以下各节仅重点介绍您需要直接从模板内调用的宏。如果您希望直接查看宏代码,该文件名为 spring.ftl,位于 org.springframework.web.servlet.view.freemarker 包中。

简单绑定

在基于 FreeMarker 模板的 HTML 表单中,这些表单充当 Spring MVC 控制器的表单视图,您可以使用与以下示例类似的代码来绑定字段值并显示每个输入字段的错误消息,其方式类似于 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> 需要一个 'path' 参数,该参数由命令对象的名称(默认是 'command',除非您在控制器配置中更改了它)后跟一个点以及您希望绑定的命令对象上的字段名称组成。您也可以使用嵌套字段,例如 command.address.streetbind 宏假定 web.xml 中由 ServletContext 参数 defaultHtmlEscape 指定的默认 HTML 转义行为。

一个名为 <@spring.bindEscaped> 的替代宏形式接受第二个参数,该参数明确指定在状态错误消息或值中是否应使用 HTML 转义。您可以根据需要将其设置为 truefalse。附加的表单处理宏简化了 HTML 转义的使用,您应尽可能使用这些宏。这些宏将在下一节中解释。

输入宏

FreeMarker 的附加便利宏简化了绑定和表单生成(包括验证错误显示)。使用这些宏生成表单输入字段并非强制,您可以将它们与简单 HTML 或直接调用我们之前强调的 Spring 绑定宏结合使用。

下表显示了可用宏、FreeMarker Template (FTL) 定义以及每个宏接受的参数列表

表 1. 宏定义表
FTL 定义

message(根据 code 参数从资源包输出字符串)

<@spring.message code/>

messageText(根据 code 参数从资源包输出字符串,如果 code 参数未找到则回退到 default 参数的值)

<@spring.messageText code, text/>

url(为相对 URL 添加应用上下文根前缀)

<@spring.url relativeUrl/>

formInput(用于收集用户输入的标准输入字段)

<@spring.formInput path, attributes, fieldType/>

formHiddenInput(用于提交非用户输入的隐藏输入字段)

<@spring.formHiddenInput path, attributes/>

formPasswordInput(用于收集密码的标准输入字段。请注意,此类字段中永远不会填充任何值。)

<@spring.formPasswordInput path, attributes/>

formTextarea(用于收集长篇自由格式文本输入的大文本字段)

<@spring.formTextarea path, attributes/>

formSingleSelect(下拉框,允许选择一个必需值)

<@spring.formSingleSelect path, options, attributes/>

formMultiSelect(列表框,允许用户选择 0 个或多个值)

<@spring.formMultiSelect path, options, attributes/>

formRadioButtons(一组单选按钮,允许从可用选项中进行单选)

<@spring.formRadioButtons path, options separator, attributes/>

formCheckboxes(一组复选框,允许选择 0 个或多个值)

<@spring.formCheckboxes path, options, separator, attributes/>

formCheckbox(单个复选框)

<@spring.formCheckbox path, attributes/>

showErrors(简化绑定字段的验证错误显示)

<@spring.showErrors separator, classOrStyle/>

在 FreeMarker 模板中,实际上不需要 formHiddenInputformPasswordInput,因为您可以使用普通的 formInput 宏,将 hiddenpassword 指定为 fieldType 参数的值。

上述任何宏的参数都具有一致的含义

  • path:要绑定到的字段名称(例如,“command.name”)

  • options:一个 Map,包含输入字段中所有可供选择的值。map 的键表示从表单 POST 回并绑定到命令对象的值。存储在键对应的 Map 对象是表单上向用户显示的标签,可能与表单 POST 回的对应值不同。通常,此类 map 由控制器作为参考数据提供。您可以根据所需行为使用任何 Map 实现。对于严格排序的 map,可以使用具有合适 ComparatorSortedMap(例如 TreeMap);对于应按插入顺序返回值的不确定 map,可以使用 LinkedHashMap 或来自 commons-collectionsLinkedMap

  • separator:当多个选项作为离散元素(单选按钮或复选框)可用时,用于分隔列表中每个元素的字符序列(例如 <br>)。

  • attributes:要包含在 HTML 标签本身内的任意标签或文本的附加字符串。此字符串由宏按字面原样输出。例如,在 textarea 字段中,您可以提供属性(例如 'rows="5" cols="60"'),或者可以传递样式信息,例如 'style="border:1px solid silver"'。

  • classOrStyle:对于 showErrors 宏,它指定包装每个错误的 span 元素使用的 CSS 类名称。如果未提供信息(或值为空),则错误将包装在 <b></b> 标签中。

以下各节概述了宏的示例。

输入字段

formInput 宏接受 path 参数(command.name)和一个附加的 attributes 参数(在即将到来的示例中为空)。该宏以及所有其他表单生成宏都会对 path 参数执行隐式 Spring 绑定。该绑定在发生新的绑定之前一直有效,因此 showErrors 宏无需再次传递 path 参数 —— 它作用于上次创建绑定的字段。

showErrors 宏接受一个 separator 参数(用于分隔给定字段上多个错误的字符),并接受第二个参数——这次是类名或样式属性。请注意,FreeMarker 可以为 attributes 参数指定默认值。以下示例展示了如何使用 formInputshowErrors

<@spring.formInput "command.name"/>
<@spring.showErrors "<br>"/>

下一个示例显示了表单片段的输出,生成了 name 字段并在表单提交时该字段没有值时显示了验证错误。验证通过 Spring 的 Validation framework 进行。

生成的 HTML 类似于以下示例

Name:
<input type="text" name="name" value="">
<br>
	<b>required</b>
<br>
<br>

formTextarea 宏的工作方式与 formInput 宏相同,并接受相同的参数列表。通常,第二个参数(attributes)用于传递样式信息或 textarearowscols 属性。

选择字段

您可以使用四个选择字段宏在 HTML 表单中生成常见的 UI 值选择输入

  • formSingleSelect

  • formMultiSelect

  • formRadioButtons

  • formCheckboxes

这四个宏都接受一个选项的 Map,其中包含表单字段的值和对应于该值的标签。值和标签可以相同。

下一个示例是 FTL 中的单选按钮。表单后备对象为此字段指定了默认值 'London',因此无需验证。当表单渲染时,可供选择的城市完整列表作为参考数据在模型中以名称 'cityMap' 提供。以下列表显示了该示例

...
Town:
<@spring.formRadioButtons "command.address.town", cityMap, ""/><br><br>

前面的列表渲染了一行单选按钮,cityMap 中的每个值对应一个,并使用 "" 作为分隔符。没有提供其他属性(宏的最后一个参数缺失)。cityMap 对 map 中的每个键值对都使用相同的 String。map 的键是表单实际作为 POST 请求参数提交的内容。map 的值是用户看到的标签。在前面的示例中,给定三个著名城市的列表以及表单后备对象中的默认值,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>

如果您的应用希望通过内部代码处理城市(例如),您可以创建具有合适键的代码 map,如下例所示

  • 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 转义

前面描述的表单宏的默认用法生成的 HTML 元素符合 HTML 4.01 标准,并使用 web.xml 文件中定义的 HTML 转义默认值(Spring 的绑定支持也使用了该默认值)。要使元素符合 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 -->