|
此版本仍在开发中,尚未被视为稳定版。如需最新稳定版本,请使用 Spring Framework 7.0.6! |
视图技术
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 配置
你可以通过在 Configuration bean 上设置相应的 bean 属性,将 FreeMarker 的 'Settings' 和 'SharedVariables' 直接传递给 FreeMarker 的 FreeMarkerConfigurer 对象(该对象由 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/> 元素。该元素主要用于在表单中显示表单支持对象(form-backing objects)的值,并展示 Web 层或业务层中 Validator 验证失败的结果。Spring 还为 FreeMarker 提供了相同功能的支持,并额外提供了便捷的宏(macros),用于直接生成表单输入元素。
绑定宏
FreeMarker 的一组标准宏定义维护在 spring-webflux.jar 文件中,因此对于经过适当配置的应用程序而言,这些宏始终可用。
Spring 模板库中定义的一些宏被视为内部(私有)宏,但在宏定义中并不存在此类作用域限制,因此所有宏对调用代码和用户模板都是可见的。以下各节仅聚焦于您需要在模板中直接调用的宏。如果您希望直接查看宏的源代码,该文件名为 spring.ftl,位于 org.springframework.web.reactive.result.view.freemarker 包中。
有关绑定支持的更多详细信息,请参阅 Spring MVC 的简单绑定。
脚本视图
Spring Framework 内置了与 Spring WebFlux 集成的功能,可配合任何基于 JSR-223 Java 脚本引擎运行的模板库使用。 下表列出了我们在不同脚本引擎上已测试过的模板库:
| 脚本库 | 脚本引擎 |
|---|---|
集成任何其他脚本引擎的基本规则是,它必须实现
ScriptEngine 和 Invocable 接口。 |
要求
您需要将脚本引擎放在类路径(classpath)中,具体细节因脚本引擎而异:
-
Nashorn JavaScript 引擎随 Java 8+ 一同提供。强烈建议使用最新的更新版本。
-
应将 JRuby 添加为依赖项以支持 Ruby。
-
Jython 应作为依赖项添加以支持 Python。
-
应添加
org.jetbrains.kotlin:kotlin-script-util依赖项,以及一个包含META-INF/services/javax.script.ScriptEngineFactory行的org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory文件,以支持 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() 原生兼容此签名,因此你可以直接调用它。
如果你的模板技术需要一些自定义配置,你可以提供一个脚本,实现自定义的渲染函数。例如,Handlebars 在使用模板之前需要先对其进行编译,并且需要一个 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 更新版本 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”的方法,即客户端接收服务器以 HTML 格式而非 JSON 格式发送的更新。 这种方法可以在无需编写大量甚至完全不编写 JavaScript 的情况下,获得单页应用(SPA)的优势。如需全面了解并深入学习,请访问它们各自的官方网站。
在 Spring WebFlux 中,视图渲染通常涉及指定一个视图和一个模型。
然而,在 HTML-over-the-wire(通过网络传输 HTML)中,一种常见的功能是发送多个 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.fragment("posts").fragment("comments").build();
}
@GetMapping
fun handle(): FragmentsRendering {
return FragmentsRendering.fragment("posts").fragment("comments").build()
}
每个片段都可以拥有一个独立的模型,并且该模型会从请求的共享模型中继承属性。
HTMX 和 Hotwire Turbo 支持通过 SSE(服务器发送事件)进行流式更新。
控制器可以使用 FragmentsRendering 创建 Flux<Fragment>,
或者通过 Publisher 将任何其他可适配为 Reactive Streams ReactiveAdapterRegistry 的响应式生产者用于创建 Flux<Fragment>。
也可以直接返回 FragmentsRendering,而无需使用 6 包装器。
JSON 和 XML
为了内容协商的目的,能够根据客户端请求的内容类型,在通过 HTML 模板渲染模型与其他格式(如 JSON 或 XML)之间进行切换是非常有用的。为了支持这一功能,Spring WebFlux 提供了HttpMessageWriterView,您可以使用它来插入来自spring-web的任何可用编解码器(Codecs),例如Jackson2JsonEncoder、Jackson2SmileEncoder或Jaxb2XmlEncoder。
与其他视图技术不同,HttpMessageWriterView 不需要 ViewResolver,而是作为默认视图进行配置。
您可以配置一个或多个此类默认视图,每个视图包装不同的 HttpMessageWriter 实例或 Encoder 实例。在运行时,将使用与请求的内容类型匹配的那个视图。
在大多数情况下,模型包含多个属性。要确定序列化哪一个属性,您可以使用要用于渲染的模型属性名称来配置 HttpMessageWriterView。如果模型仅包含一个属性,则使用该属性。