FreeMarker

Apache FreeMarker是一个模板引擎,可用于从 HTML 到电子邮件等生成任何类型的文本输出。Spring 框架有内置集成,可用于将 Spring MVC 与 FreeMarker 模板一起使用。

Apache FreeMarker is a template engine for generating any kind of text output from HTML to email and others. The Spring Framework has built-in integration for using Spring MVC with FreeMarker templates.

View Configuration

以下示例展示了如何将 FreeMarker 配置为视图技术:

The following example shows how to configure FreeMarker as a view technology:

  • 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 中配置同样的内容:

The following example shows how to configure the same in 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 以完全控制所有属性,如以下示例所示:

Alternatively, you can also declare the FreeMarkerConfigurer bean for full control over all properties, as the following example shows:

<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 模板。

Your templates need to be stored in the directory specified by the FreeMarkerConfigurer shown in the preceding example. Given the preceding configuration, if your controller returns a view name of welcome, the resolver looks for the /WEB-INF/freemarker/welcome.ftl template.

FreeMarker Configuration

你可以通过设置 FreeMarkerConfigurer bean 上的相应 bean 属性,将 FreeMarker “设置”和“共享变量”直接传递给 FreeMarker Configuration 对象(其由 Spring 管理)。freemarkerSettings 属性需要 java.util.Properties 对象,而 freemarkerVariables 属性需要 java.util.Map。以下示例展示了如何使用 FreeMarkerConfigurer

You can pass FreeMarker 'Settings' and 'SharedVariables' directly to the FreeMarker Configuration object (which is managed by Spring) by setting the appropriate bean properties on the FreeMarkerConfigurer bean. The freemarkerSettings property requires a java.util.Properties object, and the freemarkerVariables property requires a java.util.Map. The following example shows how to use a 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 对象。

See the FreeMarker documentation for details of settings and variables as they apply to the Configuration object.

Form Handling

Spring 提供了一个标签库,用于 JSP,其中包括 <spring:bind/> 元素。此元素主要允许表单显示来自表单后备对象的数值,以及显示来自 Web 或业务层中的 Validator 的不成功验证的结果。Spring 在 FreeMarker 中也支持同样的功能,并提供其他方便的宏,用于生成表单输入元素本身。

Spring provides a tag library for use in JSPs that contains, among others, a <spring:bind/> element. This element primarily lets forms display values from form-backing objects and show the results of failed validations from a Validator in the web or business tier. Spring also has support for the same functionality in FreeMarker, with additional convenience macros for generating form input elements themselves.

The Bind Macros

标准宏集为在 spring-webmvc.jar 文件中为 FreeMarker 维护,以便在适当配置的应用程序中始终可用。

A standard set of macros are maintained within the spring-webmvc.jar file for FreeMarker, so they are always available to a suitably configured application.

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

Some of the macros defined in the Spring templating libraries are considered internal (private), but no such scoping exists in the macro definitions, making all macros visible to calling code and user templates. The following sections concentrate only on the macros you need to directly call from within your templates. If you wish to view the macro code directly, the file is called spring.ftl and is in the org.springframework.web.servlet.view.freemarker package.

Simple Binding

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

In your HTML forms based on FreeMarker templates that act as a form view for a Spring MVC controller, you can use code similar to the next example to bind to field values and display error messages for each input field in similar fashion to the JSP equivalent. The following example shows a personForm view:

<!-- 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.streetbind 宏会假定在 web.xml 中由 ServletContext 参数 defaultHtmlEscape 指定的默认 HTML escape 行为。

<@spring.bind> requires a 'path' argument, which consists of the name of your command object (it is 'command', unless you changed it in your controller configuration) followed by a period and the name of the field on the command object to which you wish to bind. You can also use nested fields, such as command.address.street. The bind macro assumes the default HTML escaping behavior specified by the ServletContext parameter defaultHtmlEscape in web.xml.

称为 <@spring.bindEscaped> 的宏的另一种形式需要一个第二个参数,该参数明确指定在状态错误消息或值中是否应该使用 HTML escape。你可以根据需要将其设置为 truefalse。附加的表单处理宏简化了 HTML escape 的使用,并且你应尽可能使用这些宏。它们将在下一部分进行说明。

An alternative form of the macro called <@spring.bindEscaped> takes a second argument that explicitly specifies whether HTML escaping should be used in the status error messages or values. You can set it to true or false as required. Additional form handling macros simplify the use of HTML escaping, and you should use these macros wherever possible. They are explained in the next section.

Input Macros

用于 FreeMarker 的附加便利宏可简化绑定和表单生成(包括验证错误显示)。使用这些宏来生成表单输入字段绝不是必需的,并且你可以将它们与简单的 HTML 或直接调用我们之前突出显示的 Spring 绑定宏相混合和匹配。

Additional convenience macros for FreeMarker simplify both binding and form generation (including validation error display). It is never necessary to use these macros to generate form input fields, and you can mix and match them with simple HTML or direct calls to the Spring bind macros that we highlighted previously.

可用宏的以下表格显示了 FreeMarker Template (FTL) 定义以及每个宏需要的参数列表:

The following table of available macros shows the FreeMarker Template (FTL) definitions and the parameter list that each takes:

Table 1. Table of macro definitions
macro FTL definition

message (output a string from a resource bundle based on the code parameter)

<@spring.message code/>

messageText (output a string from a resource bundle based on the code parameter, falling back to the value of the default parameter)

<@spring.messageText code, text/>

url (prefix a relative URL with the application’s context root)

<@spring.url relativeUrl/>

formInput (standard input field for gathering user input)

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

formHiddenInput (hidden input field for submitting non-user input)

<@spring.formHiddenInput path, attributes/>

formPasswordInput (standard input field for gathering passwords. Note that no value is ever populated in fields of this type.)

<@spring.formPasswordInput path, attributes/>

formTextarea (large text field for gathering long, freeform text input)

<@spring.formTextarea path, attributes/>

formSingleSelect (drop down box of options that let a single required value be selected)

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

formMultiSelect (a list box of options that let the user select 0 or more values)

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

formRadioButtons (a set of radio buttons that let a single selection be made from the available choices)

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

formCheckboxes (a set of checkboxes that let 0 or more values be selected)

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

formCheckbox (a single checkbox)

<@spring.formCheckbox path, attributes/>

showErrors (simplify display of validation errors for the bound field)

<@spring.showErrors separator, classOrStyle/>

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

In FreeMarker templates, formHiddenInput and formPasswordInput are not actually required, as you can use the normal formInput macro, specifying hidden or password as the value for the fieldType parameter.

上述宏的所有参数都具有相一致的含义:

The parameters to any of the above macros have consistent meanings:

  • path: The name of the field to bind to (for example, "command.name")

  • options: A Map of all the available values that can be selected from in the input field. The keys to the map represent the values that are POSTed back from the form and bound to the command object. Map objects stored against the keys are the labels displayed on the form to the user and may be different from the corresponding values posted back by the form. Usually, such a map is supplied as reference data by the controller. You can use any Map implementation, depending on required behavior. For strictly sorted maps, you can use a SortedMap (such as a TreeMap) with a suitable Comparator and, for arbitrary Maps that should return values in insertion order, use a LinkedHashMap or a LinkedMap from commons-collections.

  • separator: Where multiple options are available as discreet elements (radio buttons or checkboxes), the sequence of characters used to separate each one in the list (such as <br>).

  • attributes: An additional string of arbitrary tags or text to be included within the HTML tag itself. This string is echoed literally by the macro. For example, in a textarea field, you may supply attributes (such as 'rows="5" cols="60"'), or you could pass style information such as 'style="border:1px solid silver"'.

  • classOrStyle: For the showErrors macro, the name of the CSS class that the span element that wraps each error uses. If no information is supplied (or the value is empty), the errors are wrapped in <b></b> tags.

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

The following sections outline examples of the macros.

Input Fields

formInput 宏使用 path 参数(command.name)和附加的 attributes 参数(在即将出现的示例中为空)。该宏将与此所有其他表单生成宏一起对路径参数执行 Spring 隐式绑定。绑定保持有效,直到出现新的绑定,所以 showErrors 宏不需要再次传递路径参数——它在上次创建绑定的字段上执行操作。

The formInput macro takes the path parameter (command.name) and an additional attributes parameter (which is empty in the upcoming example). The macro, along with all other form generation macros, performs an implicit Spring bind on the path parameter. The binding remains valid until a new bind occurs, so the showErrors macro does not need to pass the path parameter again — it operates on the field for which a binding was last created.

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

The showErrors macro takes a separator parameter (the characters that are used to separate multiple errors on a given field) and also accepts a second parameter — this time, a class name or style attribute. Note that FreeMarker can specify default values for the attributes parameter. The following example shows how to use the formInput and showErrors macros:

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

以下示例显示了表单片段的输出,在字段中未提交任何值的情况下生成的名称字段并在提交表单后显示验证错误。验证通过 Spring 的 Validation 框架进行。

The next example shows the output of the form fragment, generating the name field and displaying a validation error after the form was submitted with no value in the field. Validation occurs through Spring’s Validation framework.

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

The generated HTML resembles the following example:

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

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

The formTextarea macro works the same way as the formInput macro and accepts the same parameter list. Commonly, the second parameter (attributes) is used to pass style information or rows and cols attributes for the textarea.

Selection Fields

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

You can use four selection field macros to generate common UI value selection inputs in your HTML forms:

  • formSingleSelect

  • formMultiSelect

  • formRadioButtons

  • formCheckboxes

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

Each of the four macros accepts a Map of options that contains the value for the form field and the label that corresponds to that value. The value and the label can be the same.

下面的示例是 FTL 中的单选按钮。表单后备对象为此字段指定了 'London' 的默认值,所以不需要验证。呈现表单后,从城市列表中选择整个城市列表在模型中的名称 'cityMap' 下作为参考数据提供。以下清单显示了该示例:

The next example is for radio buttons in FTL. The form-backing object specifies a default value of 'London' for this field, so no validation is necessary. When the form is rendered, the entire list of cities to choose from is supplied as reference data in the model under the name 'cityMap'. The following listing shows the example:

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

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

The preceding listing renders a line of radio buttons, one for each value in cityMap, and uses a separator of "". No additional attributes are supplied (the last parameter to the macro is missing). The cityMap uses the same String for each key-value pair in the map. The map’s keys are what the form actually submits as POST request parameters. The map values are the labels that the user sees. In the preceding example, given a list of three well known cities and a default value in the form backing object, the HTML resembles the following:

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>

如果你的应用程序希望通过内部代码(例如)来处理城市,则可以创建带有适当键的代码地图,如下面的示例所示:

If your application expects to handle cities by internal codes (for example), you can create the map of codes with suitable keys, as the following example shows:

  • 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)
}

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

The code now produces output where the radio values are the relevant codes, but the user still sees the more user-friendly city names, as follows:

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 转义值,您可以在模板中(或在模型中,您在其中可以看到模板)指定两个变量。将它们指定在模板中的优势在于,在模板处理的后面,它们可以被更改为不同的值,以对表单中不同的字段提供不同的行为。

Default usage of the form macros described earlier results in HTML elements that are HTML 4.01 compliant and that use the default value for HTML escaping defined in your web.xml file, as used by Spring’s bind support. To make the elements be XHTML compliant or to override the default HTML escaping value, you can specify two variables in your template (or in your model, where they are visible to your templates). The advantage of specifying them in the templates is that they can be changed to different values later in the template processing to provide different behavior for different fields in your form.

若要对标签切换到 XHTML 合规,请为名为 xhtmlCompliant 的模型或上下文变量指定 true 值,如下例所示:

To switch to XHTML compliance for your tags, specify a value of true for a model or context variable named xhtmlCompliant, as the following example shows:

<#-- for FreeMarker -->
<#assign xhtmlCompliant = true>

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

After processing this directive, any elements generated by the Spring macros are now XHTML compliant.

以类似的方式,您可以指定每个字段的 HTML 转义,如下例所示:

In similar fashion, you can specify HTML escaping per field, as the following example shows:

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