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 組態
您可以將 FreeMarker 的「Settings」和「SharedVariables」直接傳遞到 FreeMarker Configuration
物件(由 Spring 管理),方法是在 FreeMarkerConfigurer
Bean 上設定適當的 Bean 屬性。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 樣板庫中定義的某些巨集被視為內部(私有),但巨集定義中不存在此類作用域,使得所有巨集都對呼叫程式碼和使用者樣板可見。以下章節僅著重於您需要直接從樣板內呼叫的巨集。如果您希望直接檢視巨集程式碼,檔案名為 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) 定義以及每個巨集採用的參數清單
巨集 | 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
參數(在即將到來的範例中為空)。此巨集與所有其他表單產生巨集一起,對路徑參數執行隱式 Spring 繫結。繫結保持有效,直到發生新的繫結,因此 showErrors
巨集不需要再次傳遞路徑參數 — 它對上次建立繫結的欄位進行操作。
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>
如果您的應用程式希望透過內部代碼(例如)處理城市,則可以使用適當的鍵建立代碼 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 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 -->