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();
	}

	// 配置FreeMarker...

	@Bean
	public FreeMarkerConfigurer freeMarkerConfigurer() {
		FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
		configurer.setTemplateLoaderPath("/WEB-INF/freemarker");
		return configurer;
	}
}
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

	override fun configureViewResolvers(registry: ViewResolverRegistry) {
		registry.freeMarker()
	}

	// 配置FreeMarker...

	@Bean
	fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
		setTemplateLoaderPath("/WEB-INF/freemarker")
	}
}

以下示例展示了如何在XML中进行相同的配置:

<mvc:annotation-driven/>

<mvc:view-resolvers>
	<mvc:freemarker/>
</mvc:view-resolvers>

<!-- 配置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/"/>
</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"/>

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

表单处理

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

绑定宏

在FreeMarker中,一组标准的宏被维护在spring-webmvc.jar文件中,因此它们始终可供适当配置的应用程序使用。

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

简单绑定

在基于FreeMarker模板的HTML表单中,作为Spring MVC控制器的表单视图,您可以使用类似下一个示例的代码来绑定字段值并显示每个输入字段的错误消息,类似于JSP的等效方式。以下示例显示了一个personForm视图:

<!-- FreeMarker宏必须导入到一个命名空间中。
	我们强烈建议坚持使用'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模板(FTL)定义及其各自接受的参数列表:

表1. 宏定义表
FTL定义

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

<@spring.message code/>

messageText(根据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宏,将fieldType参数的值指定为hiddenpassword

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

  • options:可以在输入字段中选择的所有可用值的Map。映射的键表示从表单POST回来并绑定到命令对象的值。存储在键下的映射对象是显示在表单上供用户选择的标签,可能与表单POST回来的相应值不同。通常,这样的映射由控制器作为参考数据提供。您可以使用任何Map实现,具体取决于所需的行为。对于严格排序的映射,可以使用具有适当ComparatorSortedMap(如TreeMap),对于应按插入顺序返回值的任意映射,可以使用commons-collections中的LinkedHashMapLinkedMap

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

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

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

以下部分概述了宏的示例。

输入字段

formInput宏接受path参数(command.name)和一个额外的attributes参数(在下面的示例中为空)。该宏以及所有其他表单生成宏都对路径参数执行隐式Spring绑定。绑定保持有效直到发生新的绑定,因此showErrors宏不需要再次传递路径参数 - 它操作的是最后创建绑定的字段。

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

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

下一个示例显示了表单片段的输出,在表单提交时生成名称字段并显示验证错误,因为字段中没有值。验证通过Spring的验证框架进行。

生成的HTML类似于以下示例:

名称:
<input type="text" name="name" value="">
<br>
	<b>必填</b>
<br>
<br>

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

选择字段

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

  • formSingleSelect

  • formMultiSelect

  • formRadioButtons

  • formCheckboxes

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

下一个示例是关于FTL中的单选按钮。表单后备对象为此字段指定了默认值“伦敦”,因此不需要验证。当表单呈现时,要选择的所有城市列表都作为模型中的参考数据提供,名称为“cityMap”。以下清单显示了示例:

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

前面的清单呈现了一行单选按钮,每个值在cityMap中,使用分隔符""。没有提供额外的属性(宏的最后一个参数缺失)。cityMap对于映射中的每个键值对都使用相同的String。映射的键是表单实际作为POST请求参数提交的内容。映射值是用户看到的标签。在上面的示例中,给定三个知名城市的列表和表单后备对象中的默认值,HTML类似于以下内容:

城市:
<input type="radio" name="address.town" value="London">伦敦</input>
<input type="radio" name="address.town" value="Paris" checked="checked">巴黎</input>
<input type="radio" name="address.town" value="New York">纽约</input>

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

  • Java

  • Kotlin

protected Map<String, ?> referenceData(HttpServletRequest request) throws Exception {
	Map<String, String> cityMap = new LinkedHashMap<>();
	cityMap.put("LDN", "伦敦");
	cityMap.put("PRS", "巴黎");
	cityMap.put("NYC", "纽约");

	Map<String, Object> model = new HashMap<>();
	model.put("cityMap", cityMap);
	return model;
}
protected fun referenceData(request: HttpServletRequest): Map<String, *> {
	val cityMap = linkedMapOf(
			"LDN" to "伦敦",
			"PRS" to "巴黎",
			"NYC" to "纽约"
	)
	return hashMapOf("cityMap" to cityMap)
}

现在的代码生成的输出中,单选按钮的值是相关代码,但用户仍然看到更友好的城市名称,如下所示:

城市:
<input type="radio" name="address.town" value="LDN">伦敦</input>
<input type="radio" name="address.town" value="PRS" checked="checked">巴黎</input>
<input type="radio" name="address.town" value="NYC">纽约</input>

HTML转义

默认情况下,前面描述的表单宏的使用会生成符合HTML 4.01标准并使用在您的web.xml文件中定义的默认HTML转义值的HTML元素,这是Spring绑定支持所使用的。要使元素符合XHTML标准或覆盖默认的HTML转义值,您可以在模板中指定两个变量(或在模型中指定,这样它们对模板可见)。在模板中指定它们的优势在于,它们可以在模板处理过程中更改为不同的值,以为表单中的不同字段提供不同的行为。

要切换到标签的XHTML兼容性,请为名为xhtmlCompliant的模型或上下文变量指定一个值true,如下例所示:

<#-- 对于FreeMarker -->
<#assign xhtmlCompliant = true>

处理此指令后,Spring宏生成的任何元素现在都符合XHTML标准。

类似地,您可以按字段指定HTML转义,如下例所示:

<#-- 在此之前,使用默认的HTML转义 -->

<#assign htmlEscape = true>
<#-- 下一个字段将使用HTML转义 -->
<@spring.formInput "command.name"/>

<#assign htmlEscape = false in spring>
<#-- 所有未来的字段将关闭HTML转义 -->