视图技术
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 的“设置”和“共享变量”直接传递给 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 脚本引擎之上运行的模板库结合使用的集成。下表显示了我们在不同脚本引擎上测试过的模板库:
脚本库 | 脚本引擎 |
---|---|
集成任何其他脚本引擎的基本规则是它必须实现 |
要求
您需要将脚本引擎添加到类路径中,具体细节因脚本引擎而异:
-
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 renderingContext
:RenderingContext
提供了对应用程序上下文、区域设置、模板加载器和 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)时,需要将 |
polyfill.js
仅定义 Handlebars 正常运行所需的 window
对象,如以下代码片段所示:
var window = {};
这个基本的 render.js
实现在使用模板之前对其进行编译。一个生产就绪的实现还应该存储和重用缓存的模板或预编译的模板。这可以在脚本端完成,以及您需要的任何自定义(例如管理模板引擎配置)。以下示例展示了如何编译模板:
function render(template, model) {
var compiledTemplate = Handlebars.compile(template);
return compiledTemplate(model);
}
查看 Spring Framework 单元测试,https://github.com/spring-projects/spring-framework/tree/main/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script[Java] 和 resources,以获取更多配置示例。
HTML 片段
HTMX 和 Hotwire 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>
创建 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
。如果模型只包含一个属性,则使用该属性。