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