FreeMarker
Apache FreeMarker 是一种模板引擎,可以生成从 HTML 到电子邮件等各种类型的文本输出。Spring 框架内置了与 FreeMarker 模板的集成功能,允许在 Spring MVC 中使用 FreeMarker 模板。
视图配置
以下示例展示了如何将FreeMarker配置为视图技术:
- Java
- Kotlin
- Xml
@Configuration
public class WebConfiguration 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
class WebConfiguration : WebMvcConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.freeMarker()
}
// Configure FreeMarker...
@Bean
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
setTemplateLoaderPath("/WEB-INF/freemarker")
setDefaultCharset(StandardCharsets.UTF_8)
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:view-resolvers>
<mvc:freemarker/>
</mvc:view-resolvers>
<!-- Configure FreeMarker... -->
<mvc:freemarker-configurer>
<mvc:template-loader-path location="/WEB-INF/freemarker"/>
</mvc:freemarker-configurer>
<!-- Alternatively, you can also declare the FreeMarkerConfigurer bean for full control over all properties -->
<!--
<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>
-->
</beans>
您的模板需要存储在前面示例中所示的FreeMarkerConfigurer所指定的目录中。根据上述配置,如果您的控制器返回的视图名称为welcome,解析器将会查找/WEB-INF/freemarker/welcome.ftl模板。
FreeMarker 配置
你可以通过在FreeMarkerConfigurerbean上设置相应的属性,直接将FreeMarker的Settings和SharedVariables传递给由Spring管理的FreeMarker``Configuration对象。freemarkerSettings属性需要一个java.util.Properties对象,而freemarkerVariables属性需要一个java.util.Map对象。以下示例展示了如何使用FreeMarkerConfigurer:
- Java
- Kotlin
- Xml
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/freemarker");
configurer.setFreemarkerVariables(Map.of("xml_escape", new XmlEscape()));
return configurer;
}
@Bean
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
setTemplateLoaderPath("/WEB-INF/freemarker")
setFreemarkerVariables(mapOf("xml_escape" to XmlEscape()))
}
<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 中也支持相同的功能,并提供了额外的便捷宏来生成表单输入元素本身。
绑定宏
在spring-webmvc.jar文件中维护了一组标准的FreeMarker宏,因此对于配置得当的应用程序来说,这些宏始终是可用的。
Spring模板库中定义的一些宏被视为内部(私有)宏,但实际上在宏的定义中并不存在这样的作用域限制,因此所有宏都对调用代码和用户模板可见。以下内容将仅关注那些需要直接在模板中调用的宏。如果你想直接查看宏的代码,该文件名为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.street。bind 宏会采用 web.xml 中的 ServletContext 参数 defaultHtmlEscape 指定的默认 HTML 转义行为。
有一种名为<@spring.bindEscaped>的宏的替代形式,它接受第二个参数,该参数明确指定是否应在状态错误消息或值中使用HTML转义。您可以根据需要将其设置为true或false。额外的表单处理宏简化了HTML转义的使用,只要可能,就应该使用这些宏。它们将在下一节中进行说明。
输入宏
FreeMarker 提供了额外的便捷宏,可以简化数据绑定和表单生成(包括验证错误显示)的过程。在生成表单输入字段时,完全没有必要使用这些宏,您也可以将它们与简单的 HTML 代码或我们之前提到的直接调用 Spring 数据绑定宏的方式相结合使用。
下表列出了可用的宏,显示了FreeMarker模板(FTL)的定义以及每个宏所接受的参数列表:
表1. 宏定义表
| 宏 | FTL 定义 |
|---|---|
message (根据代码参数从资源包中输出字符串) | <@spring.message code/> |
messageText (根据代码参数从资源包中输出字符串,如果参数不存在则使用默认值) | <@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模板中,实际上并不需要formHiddenInput和formPasswordInput,因为你可以使用普通的formInput宏,并在fieldType参数中指定hidden或password作为值。
上述任何宏的参数都具有统一的含义:
-
path: 要绑定的字段名称(例如,“command.name”)。 -
options: 一个Map,其中包含了可以从输入字段中选择的全部可用值。该Map的键表示从表单中返回并绑定到命令对象上的值。存储在键对应的Map对象中的值是显示给用户的表单标签,可能与表单返回的相应值不同。通常,这样的Map由控制器作为参考数据提供。根据所需的行为,您可以使用任何Map实现。对于需要严格排序的Map,您可以使用带有合适的Comparator的SortedMap(如TreeMap);而对于需要按插入顺序返回值的任意Map,则可以使用LinkedHashMap或来自commons-collections的LinkedHashMap。 -
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 宏接受一个分隔符参数(用于分隔给定字段上多个错误的字符),同时还接受第二个参数——这次是一个类名或样式属性。注意,FreeMarker 可以为这些属性参数指定默认值。以下示例展示了如何使用 formInput 和 showErrors 宏:
<@spring.formInput "command.name"/>
<@spring.showErrors "<br>"/>
下一个示例展示了表单片段的输出结果:当表单提交时,如果“name”字段没有填写任何值,就会显示一个验证错误。验证是通过Spring的Validation框架来完成的。
生成的HTML类似于以下示例:
Name:
<input type="text" name="name" value="">
<br>
<b>required</b>
<br>
<br>
formTextarea 宏的工作方式与 formInput 宏相同,也接受相同的参数列表。通常,第二个参数(attributes)用于传递样式信息,或者为 textarea 设置 rows 和 cols 属性。
选择字段
您可以使用四个选择字段宏在HTML表单中生成常见的UI值选择输入:
formSingleSelectformMultiSelectformRadioButtonsformCheckboxes
这四个宏中的每一个都接受一个Map类型的参数,该参数包含表单字段的值以及与该值对应的标签。值和标签可以是相同的。
下一个示例是关于超光速(FTL)中的单选按钮。表单支撑对象(form-backing object)为该字段指定了一个默认值“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转义
前面描述的表单宏的默认用法会生成符合HTML 4.01标准的HTML元素,并且会使用在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 -->