檢視技術

Spring WebFlux 中的檢視渲染是可插拔的。無論你選擇使用 Thymeleaf、FreeMarker 還是其他檢視技術,主要都是配置更改的問題。本章介紹與 Spring WebFlux 整合的檢視技術。

有關檢視渲染的更多上下文資訊,請參閱 檢視解析

Spring WebFlux 應用程式的檢視位於應用程式的內部信任邊界內。檢視可以訪問應用程式上下文中的 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()))
	}
}

有關應用於 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 對 FreeMarker 模板的表單宏支援的詳細資訊,請查閱 Spring MVC 文件的以下章節。

指令碼檢視

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+ 提供。強烈建議使用最新的可用更新版本。

  • 對於 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);
}

檢視 Spring Framework 單元測試的 Java資源,獲取更多配置示例。

HTML 片段

HTMXHotwire 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 中任何可用的 編解碼器,例如 Jackson2JsonEncoderJackson2SmileEncoderJaxb2XmlEncoder

與其他檢視技術不同,HttpMessageWriterView 不需要 ViewResolver,而是被 配置 為預設檢視。你可以配置一個或多個此類預設檢視,包裝不同的 HttpMessageWriter 例項或 Encoder 例項。在執行時,會使用與請求內容型別匹配的那個。

在大多數情況下,模型包含多個屬性。為了確定要序列化哪個屬性,你可以透過配置 HttpMessageWriterView 指定用於渲染的模型屬性名稱。如果模型只包含一個屬性,則使用該屬性。