View Technologies

在 Spring WebFlux 中,视图技术的用途是可插入的。无论你决定使用 Thymeleaf、FreeMarker 还是其他视图技术,主要取决于配置更改。本章涵盖与 Spring WebFlux 集成的视图技术。我们假设你已经熟悉View Resolution

The use of view technologies in Spring WebFlux is pluggable. Whether you decide to use Thymeleaf, FreeMarker, or some other view technology is primarily a matter of a configuration change. This chapter covers the view technologies integrated with Spring WebFlux. We assume you are already familiar with View Resolution.

Thymeleaf

Thymeleaf 是一款现代化的服务器端 Java 模板引擎,它强调自然 HTML 模板,可以通过双击在浏览器中预览,这非常有助于在无需运行服务器的情况下独立处理 UI 模板(例如,由设计人员处理)。Thymeleaf 提供了一套广泛的功能,并且正在积极开发和维护。有关更完整的介绍,请参见 Thymeleaf项目主页。

Thymeleaf is a modern server-side Java template engine that emphasizes natural HTML templates that can be previewed in a browser by double-clicking, which is very helpful for independent work on UI templates (for example, by a designer) without the need for a running server. Thymeleaf offers an extensive set of features, and it is actively developed and maintained. For a more complete introduction, see the Thymeleaf project home page.

Thymeleaf 与 Spring WebFlux 的集成由 Thymeleaf 项目管理。配置涉及几个 Bean 声明,例如 SpringResourceTemplateResolverSpringWebFluxTemplateEngine`和 `ThymeleafReactiveViewResolver。有关更多详细信息,请参阅 Thymeleaf+Spring和 WebFlux 集成 announcement

The Thymeleaf integration with Spring WebFlux is managed by the Thymeleaf project. The configuration involves a few bean declarations, such as SpringResourceTemplateResolver, SpringWebFluxTemplateEngine, and ThymeleafReactiveViewResolver. For more details, see Thymeleaf+Spring and the WebFlux integration announcement.

FreeMarker

Apache FreeMarker是用于从 HTML 生成任何种类的文本输出(到电子邮件等)的模板引擎。Spring Framework 已内置集成,以便将 Spring WebFlux 与 FreeMarker 模板一起使用。

Apache FreeMarker is a template engine for generating any kind of text output from HTML to email and others. The Spring Framework has built-in integration for using Spring WebFlux with FreeMarker templates.

View Configuration

以下示例展示了如何将 FreeMarker 配置为视图技术:

The following example shows how to configure FreeMarker as a view technology:

  • 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 模板。

Your templates need to be stored in the directory specified by the FreeMarkerConfigurer, shown in the preceding example. Given the preceding configuration, if your controller returns the view name, welcome, the resolver looks for the classpath:/templates/freemarker/welcome.ftl template.

FreeMarker Configuration

你可以通过设置 FreeMarkerConfigurer bean 上的相应 bean 属性,将 FreeMarker “设置”和“共享变量”直接传递给 FreeMarker Configuration 对象(其由 Spring 管理)。freemarkerSettings 属性需要 java.util.Properties 对象,而 freemarkerVariables 属性需要 java.util.Map。以下示例展示了如何使用 FreeMarkerConfigurer

You can pass FreeMarker 'Settings' and 'SharedVariables' directly to the FreeMarker Configuration object (which is managed by Spring) by setting the appropriate bean properties on the FreeMarkerConfigurer bean. The freemarkerSettings property requires a java.util.Properties object, and the freemarkerVariables property requires a java.util.Map. The following example shows how to use a 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 对象。

See the FreeMarker documentation for details of settings and variables as they apply to the Configuration object.

Form Handling

Spring 提供了一个标签库,用于 JSP,其中包括 <spring:bind/> 元素。此元素主要允许表单显示来自表单后备对象的数值,以及显示来自 Web 或业务层中的 Validator 的不成功验证的结果。Spring 在 FreeMarker 中也支持同样的功能,并提供其他方便的宏,用于生成表单输入元素本身。

Spring provides a tag library for use in JSPs that contains, among others, a <spring:bind/> element. This element primarily lets forms display values from form-backing objects and show the results of failed validations from a Validator in the web or business tier. Spring also has support for the same functionality in FreeMarker, with additional convenience macros for generating form input elements themselves.

The Bind Macros

针对 FreeMarker 在 spring-webflux.jar 文件中维护了一组标准宏,因此始终可用于妥善配置的应用程序。

A standard set of macros are maintained within the spring-webflux.jar file for FreeMarker, so they are always available to a suitably configured application.

Spring 模板库中定义的某些宏被视为内部(私有)宏,但在宏定义中不存在这种作用域,使所有宏对调用代码和用户模板可见。以下各节仅着重于您需要直接从模板内部调用的宏。如果您希望直接查看宏代码,该文件名为 spring.ftl,位于`org.springframework.web.reactive.result.view.freemarker` 包中。

Some of the macros defined in the Spring templating libraries are considered internal (private), but no such scoping exists in the macro definitions, making all macros visible to calling code and user templates. The following sections concentrate only on the macros you need to directly call from within your templates. If you wish to view the macro code directly, the file is called spring.ftl and is in the org.springframework.web.reactive.result.view.freemarker package.

有关绑定支持的其他详细信息,请参阅 Spring MVC 的Simple Binding

For additional details on binding support, see Simple Binding for Spring MVC.

Form Macros

有关 Spring 的 FreeMarker 模板表单宏支持详情,请参阅 Spring MVC 文档的以下部分。

For details on Spring’s form macro support for FreeMarker templates, consult the following sections of the Spring MVC documentation.

Script Views

Spring Framework 已内置集成,以便将 Spring WebFlux 与可以在 JSR-223 Java 脚本引擎上运行的任何模板库一起使用。下表显示了我们在不同的脚本引擎上测试过的模板库:

The Spring Framework has a built-in integration for using Spring WebFlux with any templating library that can run on top of the JSR-223 Java scripting engine. The following table shows the templating libraries that we have tested on different script engines:

Scripting Library Scripting Engine

Handlebars

Nashorn

Mustache

Nashorn

React

Nashorn

EJS

Nashorn

ERB

JRuby

String templates

Jython

Kotlin Script templating

Kotlin

集成其他任何脚本引擎的基本规则是它必须实现 ScriptEngineInvocable 接口。

The basic rule for integrating any other script engine is that it must implement the ScriptEngine and Invocable interfaces.

Requirements

您需要在类路径中使用脚本引擎,其详细信息因脚本引擎的不同而有所不同:

You need to have the script engine on your classpath, the details of which vary by script engine:

  • The Nashorn JavaScript engine is provided with Java 8+. Using the latest update release available is highly recommended.

  • JRuby should be added as a dependency for Ruby support.

  • Jython should be added as a dependency for Python support.

  • org.jetbrains.kotlin:kotlin-script-util dependency and a META-INF/services/javax.script.ScriptEngineFactory file containing a org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory line should be added for Kotlin script support. See this example for more detail.

你需要具有脚本模板化库。对于 JavaScript,执行此操作的一种方法是通过 WebJars

You need to have the script templating library. One way to do that for JavaScript is through WebJars.

Script Templates

您可以声明一个 ScriptTemplateConfigurer bean 来指定要使用的脚本引擎、要加载的脚本文件、调用以呈现模板的功能,等等。以下示例使用了 Mustache 模板和 Nashorn JavaScript 引擎:

You can declare a ScriptTemplateConfigurer bean to specify the script engine to use, the script files to load, what function to call to render templates, and so on. The following example uses Mustache templates and the Nashorn JavaScript engine:

  • 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 函数使用以下参数进行调用:

The render function is called with the following parameters:

  • String template: The template content

  • Map model: The view model

  • RenderingContext renderingContext: The RenderingContext that gives access to the application context, the locale, the template loader, and the URL (since 5.0)

Mustache.render() 本身与其签名兼容,因此您可以直接调用它。

Mustache.render() is natively compatible with this signature, so you can call it directly.

如果模板技术需要一些自定义项,则可以提供实现自定义渲染函数的脚本。例如, Handlerbars在使用之前需要编译模板,并且需要 polyfill才能模拟服务器端脚本引擎中不可用的某些浏览器功能。以下示例显示如何设置自定义渲染函数:

If your templating technology requires some customization, you can provide a script that implements a custom render function. For example, Handlerbars needs to compile templates before using them and requires a polyfill in order to emulate some browser facilities not available in the server-side script engine. The following example shows how to set a custom render function:

  • 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 上运行 Handlebar 或 React 等不适用于并发且专为并发设计的模板化库中使用非线程安全脚本引擎时,设置 sharedEngine 属性为 false 是必需的。在这种情况下,由于 this bug,Java SE 8 更新 60 是必需的,但无论如何通常建议使用最近的 Java SE 修补程序版本。

Setting the sharedEngine property to false is required when using non-thread-safe script engines with templating libraries not designed for concurrency, such as Handlebars or React running on Nashorn. In that case, Java SE 8 update 60 is required, due to this bug, but it is generally recommended to use a recent Java SE patch release in any case.

polyfill.js 仅定义了 Handlebars 正确运行所需要的 window 对象,如下片段所示:

polyfill.js defines only the window object needed by Handlebars to run properly, as the following snippet shows:

var window = {};

这个基本的 render.js 实现会在使用模板之前对其进行编译。一个可投入生产的实现还应存储并重复使用缓存的模板或预编译的模板。这可以在脚本端实现,也可以实现您需要的任何自定义(例如管理模板引擎配置)。以下示例展示了如何编译模板:

This basic render.js implementation compiles the template before using it. A production ready implementation should also store and reused cached templates or pre-compiled templates. This can be done on the script side, as well as any customization you need (managing template engine configuration for example). The following example shows how compile a template:

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] 和 资源,以获取更多配置示例。

Check out the Spring Framework unit tests, Java, and resources, for more configuration examples.

JSON and XML

对于Content Negotiation目的,能够根据客户端请求的内容类型,交替使用 HTML 模板或其他格式(例如 JSON 或 XML)来渲染模型非常有用。为了支持此操作,Spring WebFlux 提供`HttpMessageWriterView`,你可以使用它来插入任何可用的Codecs从`spring-web`,例如`Jackson2JsonEncoder`、Jackson2SmileEncoder`或`Jaxb2XmlEncoder

For Content Negotiation purposes, it is useful to be able to alternate between rendering a model with an HTML template or as other formats (such as JSON or XML), depending on the content type requested by the client. To support doing so, Spring WebFlux provides the HttpMessageWriterView, which you can use to plug in any of the available Codecs from spring-web, such as Jackson2JsonEncoder, Jackson2SmileEncoder, or Jaxb2XmlEncoder.

与其他视图技术不同,HttpMessageWriterView`不需要`ViewResolver,而是用作默认视图configured。你可以配置一个或多个此类默认视图,包装不同的`HttpMessageWriter`实例或`Encoder`实例。与请求的内容类型匹配的那个将在运行时使用。

Unlike other view technologies, HttpMessageWriterView does not require a ViewResolver but is instead configured as a default view. You can configure one or more such default views, wrapping different HttpMessageWriter instances or Encoder instances. The one that matches the requested content type is used at runtime.

在大多数场景中,模型包含多个属性。为了确定序列化哪一个,您可以用要用于渲染的模型属性的名称来配置 HttpMessageWriterView。如果模型只包含一个属性,那么就使用该属性。

In most cases, a model contains multiple attributes. To determine which one to serialize, you can configure HttpMessageWriterView with the name of the model attribute to use for rendering. If the model contains only one attribute, that one is used.