视图技术
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 指定用于渲染的模型属性名称。如果模型只包含一个属性,则使用该属性。