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");
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 的“設定”和“共享變數”直接傳遞給 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 中的相同功能,並提供了額外的便捷宏,用於生成表單輸入元素本身。
繫結宏
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> 需要一個“路徑”引數,該引數由您的命令物件名稱(如果未在控制器配置中更改,則為“command”)以及一個句點和您希望繫結的命令物件上的欄位名稱組成。您也可以使用巢狀欄位,例如 command.address.street。bind 宏假定 web.xml 中 ServletContext 引數 defaultHtmlEscape 指定的預設 HTML 轉義行為。
一個名為 <@spring.bindEscaped> 的宏的替代形式接受第二個引數,該引數明確指定是否應在狀態錯誤訊息或值中使用 HTML 轉義。您可以根據需要將其設定為 true 或 false。額外的表單處理宏簡化了 HTML 轉義的使用,您應該儘可能使用這些宏。它們將在下一節中解釋。
輸入宏
FreeMarker 的額外便捷宏簡化了繫結和表單生成(包括驗證錯誤顯示)。永遠不需要使用這些宏來生成表單輸入欄位,您可以將它們與簡單的 HTML 或直接呼叫我們之前強調的 Spring 繫結宏混合使用。
下表顯示了可用宏的 FreeMarker Template (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 的鍵表示從表單 POST 回並繫結到命令物件的值。與鍵一起儲存的 Map 物件是顯示在表單上給使用者的標籤,並且可能與表單 POST 回的相應值不同。通常,此 Map 由控制器作為參考資料提供。您可以根據所需的行為使用任何Map實現。對於嚴格排序的 Map,您可以使用帶有適當Comparator的SortedMap(例如TreeMap),對於應按插入順序返回值任意 Map,請使用LinkedHashMap或commons-collections中的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 引數(在即將到來的示例中為空)。該宏以及所有其他表單生成宏都在 path 引數上執行隱式 Spring 繫結。繫結在發生新繫結之前保持有效,因此 showErrors 宏不需要再次傳遞 path 引數 —— 它作用於上次建立繫結的欄位。
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)用於傳遞樣式資訊或 textarea 的 rows 和 cols 屬性。
選擇欄位
您可以使用四個選擇欄位宏在 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>
如果您的應用程式希望透過內部程式碼(例如)處理城市,您可以建立帶有合適鍵的程式碼對映,如下例所示
-
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 -->