|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
FreeMarker
Apache FreeMarker 是一个模板引擎,可用于生成各种类型的文本输出,包括 HTML、电子邮件等。Spring Framework 内置了对在 Spring MVC 中使用 FreeMarker 模板的支持。
视图配置
以下示例展示了如何将 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");
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")
}
}
以下示例展示了如何在 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/"/>
</bean>
您的模板需要存储在前例中所示的 FreeMarkerConfigurer 所指定的目录中。根据上述配置,如果您的控制器返回视图名称 welcome,解析器将查找 /WEB-INF/freemarker/welcome.ftl 模板。
FreeMarker 配置
你可以通过在 Configuration bean 上设置相应的 bean 属性,将 FreeMarker 的 'Settings' 和 'SharedVariables' 直接传递给 FreeMarker 的 FreeMarkerConfigurer 对象(该对象由 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/> 元素。该元素主要用于在表单中显示表单支持对象(form-backing objects)的值,并展示 Web 层或业务层中 Validator 验证失败的结果。Spring 还为 FreeMarker 提供了相同功能的支持,并额外提供了便捷的宏(macros),用于直接生成表单输入元素。
绑定宏
FreeMarker 的一组标准宏定义维护在 spring-webmvc.jar 文件中,因此对于经过适当配置的应用程序而言,这些宏始终可用。
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 宏会采用 ServletContext 中 defaultHtmlEscape 参数 web.xml 所指定的默认 HTML 转义行为。
另一种形式的宏称为 <@spring.bindEscaped>,它接受第二个参数,
用于显式指定在状态错误消息或值中是否应使用 HTML 转义。您可以根据需要将其设置为 true 或 false。
额外的表单处理宏简化了 HTML 转义的使用,您应尽可能使用这些宏。
它们将在下一节中进行说明。
输入宏
FreeMarker 提供了额外的便捷宏,用于简化数据绑定和表单生成(包括验证错误信息的显示)。使用这些宏来生成表单输入字段并非必需,您可以将它们与普通 HTML 或之前介绍过的直接调用 Spring 绑定宏的方式混合使用。
下表列出了可用的宏,展示了每个宏对应的 FreeMarker 模板(FTL)定义及其参数列表:
| 宏 | FTL 定义 |
|---|---|
|
<@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 模板中,formHiddenInput 和 formPasswordInput 实际上并不是必需的,因为你可以使用普通的 formInput 宏,并将 hidden 参数的值指定为 password 或 fieldType。 |
上述任意宏的参数具有统一的含义:
-
path:要绑定到的字段名称(例如,“command.name”) -
options:一个Map,包含输入字段中所有可供选择的值。该映射的键代表从表单提交并绑定到命令对象的值。存储在键对应的映射对象是显示在表单上供用户查看的标签,可能与表单提交回来的相应值不同。通常,此类映射由控制器作为参考数据提供。根据所需行为,您可以使用任何Map实现。对于严格排序的映射,您可以使用带有合适Comparator的SortedMap(例如TreeMap);对于需要按插入顺序返回值的任意映射,请使用来自commons-collections的LinkedHashMap或LinkedMap。 -
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 参数(在接下来的示例中为空)。该宏与所有其他表单生成宏一样,会对 showErrors 参数执行隐式的 Spring 绑定。此绑定会一直有效,直到发生新的绑定为止,因此 5 宏无需再次传递 6 参数——它会直接作用于最近一次创建绑定的字段。
showErrors 宏接受一个分隔符参数(用于分隔同一字段上的多个错误信息的字符),还接受第二个参数——这次是一个类名或样式属性。请注意,FreeMarker 可以为 attributes 参数指定默认值。以下示例展示了如何使用 formInput 和 showErrors 宏:
<@spring.formInput "command.name"/>
<@spring.showErrors "<br>"/>
下一个示例展示了表单片段的输出,生成了名称字段,并在提交表单时该字段为空的情况下显示验证错误。验证通过 Spring 的验证框架进行。
生成的 HTML 类似于以下示例:
Name:
<input type="text" name="name" value="">
<br>
<b>required</b>
<br>
<br>
formTextarea 宏的使用方式与 formInput 宏相同,并接受相同的参数列表。通常,第二个参数(attributes)用于传递样式信息,或为 rows 元素指定 cols 和 textarea 属性。
选择字段
您可以使用四个选择字段宏,在 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 转义
如前所述,默认使用表单宏(form macros)会生成符合 HTML 4.01 标准的 HTML 元素,并采用 web.xml 文件中定义的默认 HTML 转义值(该值由 Spring 的绑定支持所使用)。若要使生成的元素符合 XHTML 标准,或覆盖默认的 HTML 转义值,您可以在模板中(或在模型中,只要模板可以访问到这些变量)指定两个变量。在模板中指定这些变量的优势在于,在模板处理过程中,您可以随时将它们更改为不同的值,从而为表单中的不同字段提供不同的行为。
要使您的标签符合 XHTML 规范,请为名为 true 的模型或上下文变量指定值 xhtmlCompliant,如下例所示:
<#-- 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 -->