You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
432 lines
16 KiB
432 lines
16 KiB
[[webflux-view]] |
|
= View Technologies |
|
[.small]#xref:web/webmvc-view.adoc[See equivalent in the Servlet stack]# |
|
|
|
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 xref:web/webflux/dispatcher-handler.adoc#webflux-viewresolution[View Resolution]. |
|
|
|
|
|
|
|
|
|
[[webflux-view-thymeleaf]] |
|
== Thymeleaf |
|
[.small]#xref:web/webmvc-view/mvc-thymeleaf.adoc[See equivalent in the Servlet stack]# |
|
|
|
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 |
|
https://www.thymeleaf.org/[Thymeleaf] project home page. |
|
|
|
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 |
|
https://www.thymeleaf.org/documentation.html[Thymeleaf+Spring] and the WebFlux integration |
|
https://web.archive.org/web/20210623051330/http%3A//forum.thymeleaf.org/Thymeleaf-3-0-8-JUST-PUBLISHED-td4030687.html[announcement]. |
|
|
|
|
|
|
|
|
|
[[webflux-view-freemarker]] |
|
== FreeMarker |
|
[.small]#xref:web/webmvc-view/mvc-freemarker.adoc[See equivalent in the Servlet stack]# |
|
|
|
https://freemarker.apache.org/[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. |
|
|
|
|
|
|
|
[[webflux-view-freemarker-contextconfig]] |
|
=== View Configuration |
|
[.small]#xref:web/webmvc-view/mvc-freemarker.adoc#mvc-view-freemarker-contextconfig[See equivalent in the Servlet stack]# |
|
|
|
The following example shows how to configure FreeMarker as a view technology: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
@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; |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
@Configuration |
|
@EnableWebFlux |
|
class WebConfig : WebFluxConfigurer { |
|
|
|
override fun configureViewResolvers(registry: ViewResolverRegistry) { |
|
registry.freeMarker() |
|
} |
|
|
|
// Configure FreeMarker... |
|
|
|
@Bean |
|
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply { |
|
setTemplateLoaderPath("classpath:/templates/freemarker") |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
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. |
|
|
|
|
|
|
|
[[webflux-views-freemarker]] |
|
=== FreeMarker Configuration |
|
[.small]#xref:web/webmvc-view/mvc-freemarker.adoc#mvc-views-freemarker[See equivalent in the Servlet stack]# |
|
|
|
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`: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
@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; |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
@Configuration |
|
@EnableWebFlux |
|
class WebConfig : WebFluxConfigurer { |
|
|
|
// ... |
|
|
|
@Bean |
|
fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply { |
|
setTemplateLoaderPath("classpath:/templates") |
|
setFreemarkerVariables(mapOf("xml_escape" to XmlEscape())) |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
See the FreeMarker documentation for details of settings and variables as they apply to |
|
the `Configuration` object. |
|
|
|
|
|
|
|
[[webflux-view-freemarker-forms]] |
|
=== Form Handling |
|
[.small]#xref:web/webmvc-view/mvc-freemarker.adoc#mvc-view-freemarker-forms[See equivalent in the Servlet stack]# |
|
|
|
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. |
|
|
|
|
|
[[webflux-view-bind-macros]] |
|
==== The Bind Macros |
|
[.small]#xref:web/webmvc-view/mvc-freemarker.adoc#mvc-view-bind-macros[See equivalent in the Servlet stack]# |
|
|
|
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. |
|
|
|
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. |
|
|
|
For additional details on binding support, see xref:web/webmvc-view/mvc-freemarker.adoc#mvc-view-simple-binding[Simple Binding] |
|
for Spring MVC. |
|
|
|
|
|
[[webflux-views-form-macros]] |
|
==== Form Macros |
|
|
|
For details on Spring's form macro support for FreeMarker templates, consult the following |
|
sections of the Spring MVC documentation. |
|
|
|
* xref:web/webmvc-view/mvc-freemarker.adoc#mvc-views-form-macros[Input Macros] |
|
* xref:web/webmvc-view/mvc-freemarker.adoc#mvc-views-form-macros-input[Input Fields] |
|
* xref:web/webmvc-view/mvc-freemarker.adoc#mvc-views-form-macros-select[Selection Fields] |
|
* xref:web/webmvc-view/mvc-freemarker.adoc#mvc-views-form-macros-html-escaping[HTML Escaping] |
|
|
|
|
|
|
|
[[webflux-view-script]] |
|
== Script Views |
|
[.small]#xref:web/webmvc-view/mvc-script.adoc[See equivalent in the Servlet stack]# |
|
|
|
The Spring Framework has a built-in integration for using Spring WebFlux with any |
|
templating library that can run on top of the |
|
https://www.jcp.org/en/jsr/detail?id=223[JSR-223] Java scripting engine. |
|
The following table shows the templating libraries that we have tested on different script engines: |
|
|
|
[%header] |
|
|=== |
|
|Scripting Library |Scripting Engine |
|
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn] |
|
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn] |
|
|https://facebook.github.io/react/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn] |
|
|https://www.embeddedjs.com/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn] |
|
|https://www.stuartellis.name/articles/erb/[ERB] |https://www.jruby.org[JRuby] |
|
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython] |
|
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |https://kotlinlang.org/[Kotlin] |
|
|=== |
|
|
|
TIP: The basic rule for integrating any other script engine is that it must implement the |
|
`ScriptEngine` and `Invocable` interfaces. |
|
|
|
|
|
|
|
[[webflux-view-script-dependencies]] |
|
=== Requirements |
|
[.small]#xref:web/webmvc-view/mvc-script.adoc#mvc-view-script-dependencies[See equivalent in the Servlet stack]# |
|
|
|
You need to have the script engine on your classpath, the details of which vary by script engine: |
|
|
|
* The https://openjdk.java.net/projects/nashorn/[Nashorn] JavaScript engine is provided with |
|
Java 8+. Using the latest update release available is highly recommended. |
|
* https://www.jruby.org[JRuby] should be added as a dependency for Ruby support. |
|
* https://www.jython.org[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 |
|
https://github.com/sdeleuze/kotlin-script-templating[this example] for more detail. |
|
|
|
You need to have the script templating library. One way to do that for JavaScript is |
|
through https://www.webjars.org/[WebJars]. |
|
|
|
|
|
|
|
[[webflux-view-script-integrate]] |
|
=== Script Templates |
|
[.small]#xref:web/webmvc-view/mvc-script.adoc#mvc-view-script-integrate[See equivalent in the Servlet stack]# |
|
|
|
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: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
@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; |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
@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" |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
The `render` function is called with the following parameters: |
|
|
|
* `String template`: The template content |
|
* `Map model`: The view model |
|
* `RenderingContext renderingContext`: The |
|
{api-spring-framework}/web/servlet/view/script/RenderingContext.html[`RenderingContext`] |
|
that gives access to the application context, the locale, the template loader, and the |
|
URL (since 5.0) |
|
|
|
`Mustache.render()` is natively compatible with this signature, so you can call it directly. |
|
|
|
If your templating technology requires some customization, you can provide a script that |
|
implements a custom render function. For example, https://handlebarsjs.com[Handlerbars] |
|
needs to compile templates before using them and requires a |
|
https://en.wikipedia.org/wiki/Polyfill[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: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,indent=0,subs="verbatim,quotes",role="primary"] |
|
---- |
|
@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; |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] |
|
---- |
|
@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 |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
NOTE: 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 |
|
https://bugs.openjdk.java.net/browse/JDK-8076099[this bug], but it is generally |
|
recommended to use a recent Java SE patch release in any case. |
|
|
|
`polyfill.js` defines only the `window` object needed by Handlebars to run properly, |
|
as the following snippet shows: |
|
|
|
[source,javascript,indent=0,subs="verbatim,quotes"] |
|
---- |
|
var window = {}; |
|
---- |
|
|
|
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: |
|
|
|
[source,javascript,indent=0,subs="verbatim,quotes"] |
|
---- |
|
function render(template, model) { |
|
var compiledTemplate = Handlebars.compile(template); |
|
return compiledTemplate(model); |
|
} |
|
---- |
|
|
|
Check out the Spring Framework unit tests, |
|
{spring-framework-main-code}/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script[Java], and |
|
{spring-framework-main-code}/spring-webflux/src/test/resources/org/springframework/web/reactive/result/view/script[resources], |
|
for more configuration examples. |
|
|
|
|
|
|
|
|
|
[[webflux-view-httpmessagewriter]] |
|
== JSON and XML |
|
[.small]#xref:web/webmvc-view/mvc-jackson.adoc[See equivalent in the Servlet stack]# |
|
|
|
For xref:web/webflux/dispatcher-handler.adoc#webflux-multiple-representations[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 |
|
xref:web/webflux/reactive-spring.adoc#webflux-codecs[Codecs] from `spring-web`, such as `Jackson2JsonEncoder`, `Jackson2SmileEncoder`, |
|
or `Jaxb2XmlEncoder`. |
|
|
|
Unlike other view technologies, `HttpMessageWriterView` does not require a `ViewResolver` |
|
but is instead xref:web/webflux/config.adoc#webflux-config-view-resolvers[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. |
|
|
|
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.
|
|
|