檢視技術
Spring WebFlux 中的檢視渲染是可插拔的。無論你選擇使用 Thymeleaf、FreeMarker 還是其他檢視技術,主要都是配置更改的問題。本章介紹與 Spring WebFlux 整合的檢視技術。
有關檢視渲染的更多上下文資訊,請參閱 檢視解析。
Spring WebFlux 應用程式的檢視位於應用程式的內部信任邊界內。檢視可以訪問應用程式上下文中的 bean,因此,我們不建議在外部源可以編輯模板的應用程式中使用 Spring WebFlux 模板支援,因為這可能存在安全隱患。 |
Thymeleaf
Thymeleaf 是一個現代的伺服器端 Java 模板引擎,強調自然 HTML 模板,可以在瀏覽器中透過雙擊預覽,這對於獨立進行 UI 模板工作(例如,由設計師)而無需執行伺服器非常有用。Thymeleaf 提供了豐富的功能集,並且正在積極開發和維護中。有關更完整的介紹,請參閱 Thymeleaf 專案主頁。
Thymeleaf 與 Spring WebFlux 的整合由 Thymeleaf 專案管理。配置涉及一些 bean 宣告,例如 SpringResourceTemplateResolver
、SpringWebFluxTemplateEngine
和 ThymeleafReactiveViewResolver
。有關更多詳細資訊,請參閱 Thymeleaf+Spring 以及 WebFlux 整合公告。
FreeMarker
Apache FreeMarker 是一個模板引擎,用於生成從 HTML 到電子郵件等各種文字輸出。Spring Framework 內建了使用 Spring WebFlux 與 FreeMarker 模板整合的支援。
檢視配置
以下示例展示瞭如何將 FreeMarker 配置為檢視技術
-
Java
-
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.freeMarker();
}
// Configure FreeMarker...
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("classpath:/templates/freemarker");
return configurer;
}
}
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.freeMarker()
}
// Configure FreeMarker...
@Bean
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
setTemplateLoaderPath("classpath:/templates/freemarker")
}
}
你的模板需要儲存在 FreeMarkerConfigurer
指定的目錄中,如前面的示例所示。根據前面的配置,如果你的控制器返回檢視名稱 welcome
,解析器將查詢 classpath:/templates/freemarker/welcome.ftl
模板。
FreeMarker 配置
你可以透過在 FreeMarkerConfigurer
bean 上設定相應的 bean 屬性,將 FreeMarker 的 'Settings' 和 'SharedVariables' 直接傳遞給 FreeMarker 的 Configuration
物件(由 Spring 管理)。freemarkerSettings
屬性需要一個 java.util.Properties
物件,而 freemarkerVariables
屬性需要一個 java.util.Map
。以下示例展示瞭如何使用 FreeMarkerConfigurer
-
Java
-
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
// ...
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
Map<String, Object> variables = new HashMap<>();
variables.put("xml_escape", new XmlEscape());
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("classpath:/templates");
configurer.setFreemarkerVariables(variables);
return configurer;
}
}
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
// ...
@Bean
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
setTemplateLoaderPath("classpath:/templates")
setFreemarkerVariables(mapOf("xml_escape" to XmlEscape()))
}
}
有關應用於 Configuration
物件的設定和變數的詳細資訊,請參閱 FreeMarker 文件。
表單處理
Spring 提供了一個用於 JSP 的標籤庫,其中包含(除其他外)一個 <spring:bind/>
元素。此元素主要用於表單顯示來自表單支援物件的 值,並顯示來自 Web 或業務層的 Validator
的驗證失敗結果。Spring 在 FreeMarker 中也支援相同的功能,並提供了用於生成表單輸入元素本身的額外便利宏。
繫結宏
spring-webflux.jar
檔案中維護了一套標準的 FreeMarker 宏,因此它們總是可用於適當配置的應用程式。
Spring 模板庫中定義的一些宏被認為是內部(私有)的,但在宏定義中不存在這樣的作用域,使得所有宏對於呼叫程式碼和使用者模板都可見。以下章節僅關注你需要從模板中直接呼叫的宏。如果你希望直接檢視宏程式碼,該檔名為 spring.ftl
,位於 org.springframework.web.reactive.result.view.freemarker
包中。
有關繫結支援的更多詳細資訊,請參閱 Spring MVC 的 簡單繫結。
指令碼檢視
Spring Framework 內建了對使用 Spring WebFlux 與任何可以在 JSR-223 Java 指令碼引擎之上執行的模板庫進行整合的支援。下表顯示了我們在不同指令碼引擎上測試過的模板庫
指令碼庫 | 指令碼引擎 |
---|---|
整合任何其他指令碼引擎的基本規則是它必須實現 ScriptEngine 和 Invocable 介面。 |
要求
你需要將指令碼引擎放在你的類路徑中,具體細節因指令碼引擎而異
-
Nashorn JavaScript 引擎隨 Java 8+ 提供。強烈建議使用最新的可用更新版本。
-
對於 Ruby 支援,應將 JRuby 新增為依賴項。
-
對於 Python 支援,應將 Jython 新增為依賴項。
-
對於 Kotlin 指令碼支援,應新增
org.jetbrains.kotlin:kotlin-script-util
依賴項以及一個包含org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory
行的META-INF/services/javax.script.ScriptEngineFactory
檔案。有關更多詳細資訊,請參閱此示例。
你需要擁有指令碼模板庫。對於 JavaScript,一種方法是透過 WebJars。
指令碼模板
你可以宣告一個 ScriptTemplateConfigurer
bean 來指定要使用的指令碼引擎、要載入的指令碼檔案、呼叫哪個函式來渲染模板等等。以下示例使用 Mustache 模板和 Nashorn JavaScript 引擎
-
Java
-
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("mustache.js");
configurer.setRenderObject("Mustache");
configurer.setRenderFunction("render");
return configurer;
}
}
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.scriptTemplate()
}
@Bean
fun configurer() = ScriptTemplateConfigurer().apply {
engineName = "nashorn"
setScripts("mustache.js")
renderObject = "Mustache"
renderFunction = "render"
}
}
render
函式使用以下引數呼叫
-
String template
: 模板內容 -
Map model
: 檢視模型 -
RenderingContext renderingContext
: 提供訪問應用程式上下文、區域設定、模板載入器和 URL 的RenderingContext
(從 5.0 開始)
Mustache.render()
本身就與此簽名相容,因此你可以直接呼叫它。
如果你的模板技術需要一些自定義,你可以提供一個實現自定義渲染函式的指令碼。例如,Handlerbars 在使用模板之前需要先編譯它們,並且需要一個polyfill 來模擬伺服器端指令碼引擎中不可用的一些瀏覽器功能。以下示例展示瞭如何設定自定義渲染函式
-
Java
-
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
configurer.setRenderFunction("render");
configurer.setSharedEngine(false);
return configurer;
}
}
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.scriptTemplate()
}
@Bean
fun configurer() = ScriptTemplateConfigurer().apply {
engineName = "nashorn"
setScripts("polyfill.js", "handlebars.js", "render.js")
renderFunction = "render"
isSharedEngine = false
}
}
當使用非執行緒安全的指令碼引擎和未設計用於併發的模板庫(例如在 Nashorn 上執行的 Handlebars 或 React)時,需要將 sharedEngine 屬性設定為 false 。在這種情況下,由於此 bug,需要 Java SE 8 update 60,但通常建議無論如何都使用最新的 Java SE 補丁版本。 |
polyfill.js
僅定義了 Handlebars 正常執行所需的 window
物件,如下面的程式碼片段所示
var window = {};
這個基本的 render.js
實現在使用模板之前先編譯它。一個生產就緒的實現還應該儲存和重用快取的模板或預編譯的模板。這可以在指令碼端完成,以及你需要的任何自定義(例如管理模板引擎配置)。以下示例展示瞭如何編譯模板
function render(template, model) {
var compiledTemplate = Handlebars.compile(template);
return compiledTemplate(model);
}
HTML 片段
HTMX 和 Hotwire Turbo 強調一種“HTML over the wire”方法,客戶端接收伺服器的更新是 HTML 格式而不是 JSON 格式。這使得無需編寫大量甚至任何 JavaScript 即可獲得 SPA(單頁應用)的好處。要獲得良好的概覽並瞭解更多資訊,請訪問它們各自的網站。
在 Spring WebFlux 中,檢視渲染通常涉及指定一個檢視和一個模型。然而,在 HTML over the wire 中,一個常見的功能是傳送多個 HTML 片段,瀏覽器可以使用這些片段更新頁面的不同部分。為此,控制器方法可以返回 Collection<Fragment>
。例如
-
Java
-
Kotlin
@GetMapping
List<Fragment> handle() {
return List.of(Fragment.create("posts"), Fragment.create("comments"));
}
@GetMapping
fun handle(): List<Fragment> {
return listOf(Fragment.create("posts"), Fragment.create("comments"))
}
透過返回專用型別 FragmentsRendering
也可以做到同樣的事情
-
Java
-
Kotlin
@GetMapping
FragmentsRendering handle() {
return FragmentsRendering.with("posts").fragment("comments").build();
}
@GetMapping
fun handle(): FragmentsRendering {
return FragmentsRendering.with("posts").fragment("comments").build()
}
每個片段都可以有一個獨立的模型,並且該模型繼承了請求的共享模型的屬性。
HTMX 和 Hotwire Turbo 支援透過 SSE(伺服器傳送事件)進行流式更新。控制器可以使用 Flux<Fragment>
建立 FragmentsRendering
,或者透過 ReactiveAdapterRegistry
將任何其他響應式生產者適配到 Reactive Streams Publisher
。也可以直接返回 Flux<Fragment>
而無需 FragmentsRendering
包裝。
JSON 和 XML
出於 內容協商 的目的,根據客戶端請求的內容型別,能夠在 HTML 模板或其他格式(如 JSON 或 XML)之間交替渲染模型非常有用。為了支援這一點,Spring WebFlux 提供了 HttpMessageWriterView
,你可以使用它來插入 spring-web
中任何可用的 編解碼器,例如 Jackson2JsonEncoder
、Jackson2SmileEncoder
或 Jaxb2XmlEncoder
。
與其他檢視技術不同,HttpMessageWriterView
不需要 ViewResolver
,而是被 配置 為預設檢視。你可以配置一個或多個此類預設檢視,包裝不同的 HttpMessageWriter
例項或 Encoder
例項。在執行時,會使用與請求內容型別匹配的那個。
在大多數情況下,模型包含多個屬性。為了確定要序列化哪個屬性,你可以透過配置 HttpMessageWriterView
指定用於渲染的模型屬性名稱。如果模型只包含一個屬性,則使用該屬性。