檢視技術

Spring WebFlux 中檢視的呈現是可插拔的。無論您決定使用 Thymeleaf、FreeMarker 還是其他檢視技術,主要都是組態變更的問題。本章涵蓋與 Spring WebFlux 整合的檢視技術。

有關檢視呈現的更多背景資訊,請參閱 檢視解析

Spring WebFlux 應用程式的檢視存在於應用程式的內部信任邊界內。檢視可以存取應用程式 Context 中的 Bean,因此,我們不建議在範本可由外部來源編輯的應用程式中使用 Spring WebFlux 範本支援,因為這可能會產生安全性隱憂。

Thymeleaf

Thymeleaf 是一個現代化的伺服器端 Java 範本引擎,強調自然 HTML 範本,這些範本可以透過雙擊在瀏覽器中預覽,這對於獨立處理 UI 範本(例如,由設計師處理)而無需執行伺服器非常有幫助。Thymeleaf 提供了廣泛的功能集,並且正在積極開發和維護中。如需更完整的簡介,請參閱 Thymeleaf 專案首頁。

Thymeleaf 與 Spring WebFlux 的整合由 Thymeleaf 專案管理。組態涉及一些 Bean 宣告,例如 SpringResourceTemplateResolverSpringWebFluxTemplateEngineThymeleafReactiveViewResolver。如需更多詳細資訊,請參閱 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()))
	}
}

請參閱 FreeMarker 文件,以了解設定和變數的詳細資訊,因為它們適用於 Configuration 物件。

表單處理

Spring 提供了一個標籤程式庫,用於 JSP 中,其中包含 <spring:bind/> 元素等。此元素主要讓表單顯示來自表單後端物件的值,並顯示來自 Web 或業務層中 Validator 的失敗驗證結果。Spring 也支援 FreeMarker 中相同的功能,並為產生表單輸入元素本身提供了額外的便利巨集。

繫結巨集

標準巨集集維護在 FreeMarker 的 spring-webflux.jar 檔案中,因此它們始終可用於適當組態的應用程式。

Spring 範本程式庫中定義的一些巨集被認為是內部的(私有的),但巨集定義中不存在此類作用域,使得所有巨集對於呼叫程式碼和使用者範本都是可見的。以下章節僅著重於您需要直接從範本中呼叫的巨集。如果您希望直接檢視巨集程式碼,則檔案名為 spring.ftl,位於 org.springframework.web.reactive.result.view.freemarker 套件中。

有關繫結支援的更多詳細資訊,請參閱 Spring MVC 的 簡單繫結

表單巨集

有關 Spring 對於 FreeMarker 範本的表單巨集支援的詳細資訊,請查閱 Spring MVC 文件中的以下章節。

Script Views

Spring Framework 內建了將 Spring WebFlux 與任何可以在 JSR-223 Java 腳本引擎之上執行的範本程式庫整合。下表顯示了我們在不同腳本引擎上測試過的範本程式庫

腳本程式庫 腳本引擎

Handlebars

Nashorn

Mustache

Nashorn

React

Nashorn

EJS

Nashorn

ERB

JRuby

字串範本

Jython

Kotlin 腳本範本

Kotlin

整合任何其他腳本引擎的基本規則是它必須實作 ScriptEngineInvocable 介面。

需求

您需要在您的類別路徑上具有腳本引擎,其詳細資訊因腳本引擎而異

  • Nashorn JavaScript 引擎隨 Java 8+ 提供。強烈建議使用可用的最新更新版本。

  • 應新增 JRuby 作為 Ruby 支援的相依性。

  • 應新增 Jython 作為 Python 支援的相依性。

  • 應新增 org.jetbrains.kotlin:kotlin-script-util 相依性和包含 org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory 行的 META-INF/services/javax.script.ScriptEngineFactory 檔案,以支援 Kotlin 腳本。有關更多詳細資訊,請參閱 此範例

您需要有腳本範本程式庫。對於 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 renderingContextRenderingContext,可讓您存取應用程式 Context、地區設定、範本載入器和 URL(自 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。在這種情況下,由於 這個錯誤,需要 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);
}

請查看 Spring Framework 單元測試、Java資源,以取得更多組態範例。

HTML 片段

HTMXHotwire Turbo 強調 HTML-over-the-wire 方法,其中客户端接收 HTML 中的伺服器更新,而不是 JSON 中的伺服器更新。這允許 SPA(單頁應用程式)的優點,而無需編寫太多甚至任何 JavaScript。如需良好的總覽並了解更多資訊,請造訪它們各自的網站。

在 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> 或任何其他可調整為 Reactive Streams Publisher 的反應式產生器(透過 ReactiveAdapterRegistry)建立 FragmentsRendering。也可以直接傳回 Flux<Fragment>,而無需 FragmentsRendering 包裝器。

JSON 和 XML

為了 內容協商 目的,能夠根據客户端請求的內容類型,在使用 HTML 範本呈現模型或以其他格式(例如 JSON 或 XML)呈現模型之間切換很有用。為了支援這樣做,Spring WebFlux 提供了 HttpMessageWriterView,您可以使用它插入來自 spring-web 的任何可用 編解碼器,例如 Jackson2JsonEncoderJackson2SmileEncoderJaxb2XmlEncoder

與其他檢視技術不同,HttpMessageWriterView 不需要 ViewResolver,而是 組態 為預設檢視。您可以組態一個或多個此類預設檢視,包裝不同的 HttpMessageWriter 實例或 Encoder 實例。在執行時使用與請求的內容類型相符的檢視。

在大多數情況下,模型包含多個屬性。為了確定要序列化哪個屬性,您可以將 HttpMessageWriterView 組態為使用模型屬性的名稱進行呈現。如果模型僅包含一個屬性,則使用該屬性。