3 changed files with 331 additions and 38 deletions
@ -0,0 +1,288 @@ |
|||||||
|
[[webflux-view]] |
||||||
|
= View Technologies |
||||||
|
[.small]#<<web.adoc#mvc-view,Same in Spring MVC>># |
||||||
|
|
||||||
|
The use of view technologies in Spring WebFlux is pluggable, whether you decide to |
||||||
|
use Thymeleaf, FreeMarker, or other, is primarily a matter of a configuration change. |
||||||
|
This chapter covers view technologies integrated with Spring WebFlux. We assume you are |
||||||
|
already familiar with <<webflux-viewresolution>>. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[webflux-view-thymeleaf]] |
||||||
|
== Thymeleaf |
||||||
|
[.small]#<<web.adoc#mvc-view-thymeleaf,Same in Spring MVC>># |
||||||
|
|
||||||
|
Thymeleaf is 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, e.g. by 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 |
||||||
|
http://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 |
||||||
|
http://www.thymeleaf.org/documentation.html[Thymeleaf+Spring] and the WebFlux integration |
||||||
|
http://forum.thymeleaf.org/Thymeleaf-3-0-8-JUST-PUBLISHED-td4030687.html[announcement]. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[webflux-view-freemarker]] |
||||||
|
== FreeMarker |
||||||
|
[.small]#<<web.adoc#mvc-view-freemarker,Same in Spring MVC>># |
||||||
|
|
||||||
|
http://www.freemarker.org[Apache FreeMarker] is a template engine for generating any |
||||||
|
kind of text output from HTML to email, and others. The Spring Framework has a built-in |
||||||
|
integration for using Spring WebFlux with FreeMarker templates. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[webflux-view-freemarker-contextconfig]] |
||||||
|
=== View config |
||||||
|
[.small]#<<web.adoc#mvc-view-freemarker-contextconfig,Same in Spring MVC>># |
||||||
|
|
||||||
|
To configure FreeMarker as a view technology: |
||||||
|
|
||||||
|
[source,java,indent=0] |
||||||
|
[subs="verbatim,quotes"] |
||||||
|
---- |
||||||
|
@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"); |
||||||
|
return configurer; |
||||||
|
} |
||||||
|
} |
||||||
|
---- |
||||||
|
|
||||||
|
Your templates need to be stored in the directory specified by the `FreeMarkerConfigurer` |
||||||
|
shown above. Given the above configuration if your controller returns the view name |
||||||
|
"welcome" then the resolver will look for the |
||||||
|
`classpath:/templates/freemarker/welcome.ftl` template. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[webflux-views-freemarker]] |
||||||
|
=== FreeMarker config |
||||||
|
[.small]#<<web.adoc#mvc-views-freemarker,Same in Spring MVC>># |
||||||
|
|
||||||
|
FreeMarker 'Settings' and 'SharedVariables' can be passed directly to the FreeMarker |
||||||
|
`Configuration` object 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`. |
||||||
|
|
||||||
|
[source,java,indent=0] |
||||||
|
[subs="verbatim,quotes"] |
||||||
|
---- |
||||||
|
@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; |
||||||
|
} |
||||||
|
} |
||||||
|
---- |
||||||
|
|
||||||
|
See the FreeMarker documentation for details of settings and variables as they apply to |
||||||
|
the `Configuration` object. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[webflux-view-script]] |
||||||
|
== Script Views |
||||||
|
[.small]#<<web.adoc#mvc-view-script,Same in Spring MVC>># |
||||||
|
|
||||||
|
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. Below is a list |
||||||
|
of templating libraries we've tested on different script engines: |
||||||
|
|
||||||
|
[horizontal] |
||||||
|
http://handlebarsjs.com/[Handlebars] :: http://openjdk.java.net/projects/nashorn/[Nashorn] |
||||||
|
https://mustache.github.io/[Mustache] :: http://openjdk.java.net/projects/nashorn/[Nashorn] |
||||||
|
http://facebook.github.io/react/[React] :: http://openjdk.java.net/projects/nashorn/[Nashorn] |
||||||
|
http://www.embeddedjs.com/[EJS] :: http://openjdk.java.net/projects/nashorn/[Nashorn] |
||||||
|
http://www.stuartellis.eu/articles/erb/[ERB] :: http://jruby.org[JRuby] |
||||||
|
https://docs.python.org/2/library/string.html#template-strings[String templates] :: http://www.jython.org/[Jython] |
||||||
|
|
||||||
|
[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]#<<web.adoc#mvc-view-script-dependencies,Same in Spring MVC>># |
||||||
|
|
||||||
|
You need to have the script engine on your classpath: |
||||||
|
|
||||||
|
* http://openjdk.java.net/projects/nashorn/[Nashorn] JavaScript engine is provided with |
||||||
|
Java 8+. Using the latest update release available is highly recommended. |
||||||
|
* http://jruby.org[JRuby] should be added as a dependency for Ruby support. |
||||||
|
* http://www.jython.org[Jython] should be added as a dependency for Python support. |
||||||
|
|
||||||
|
You need to have the script templating library. One way to do that for Javascript is |
||||||
|
through http://www.webjars.org/[WebJars]. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[webflux-view-script-integrate]] |
||||||
|
=== Script templates |
||||||
|
[.small]#<<web.adoc#mvc-view-script-integrate,Same in Spring MVC>># |
||||||
|
|
||||||
|
Declare a `ScriptTemplateConfigurer` bean in order to specify the script engine to use, |
||||||
|
the script files to load, what function to call to render templates, and so on. |
||||||
|
Below is an example with Mustache templates and the Nashorn JavaScript engine: |
||||||
|
|
||||||
|
[source,java,indent=0] |
||||||
|
[subs="verbatim,quotes"] |
||||||
|
---- |
||||||
|
@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; |
||||||
|
} |
||||||
|
} |
||||||
|
---- |
||||||
|
|
||||||
|
The render function is called with the following parameters: |
||||||
|
|
||||||
|
* `String template`: the template content |
||||||
|
* `Map model`: the view model |
||||||
|
* `String url`: the template url (since 4.2.2) |
||||||
|
|
||||||
|
`Mustache.render()` is natively compatible with this signature, so you can call it directly. |
||||||
|
|
||||||
|
If your templating technology requires some customization, you may provide a script that |
||||||
|
implements a custom render function. For example, http://handlebarsjs.com[Handlerbars] |
||||||
|
needs to compile templates before using them, and requires a |
||||||
|
http://en.wikipedia.org/wiki/Polyfill[polyfill] in order to emulate some |
||||||
|
browser facilities not available in the server-side script engine. |
||||||
|
|
||||||
|
[source,java,indent=0] |
||||||
|
[subs="verbatim,quotes"] |
||||||
|
---- |
||||||
|
@Configuration |
||||||
|
@EnableWebMvc |
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
|
---- |
||||||
|
|
||||||
|
[NOTE] |
||||||
|
==== |
||||||
|
Setting the `sharedEngine` property to `false` is required when using non thread-safe |
||||||
|
script engines with templating libraries not designed for concurrency, like Handlebars or |
||||||
|
React running on Nashorn for example. In that case, Java 8u60 or greater is required due |
||||||
|
to https://bugs.openjdk.java.net/browse/JDK-8076099[this bug]. |
||||||
|
==== |
||||||
|
|
||||||
|
`polyfill.js` only defines the `window` object needed by Handlebars to run properly: |
||||||
|
|
||||||
|
[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 / pre-compiled templates. |
||||||
|
This can be done on the script side, as well as any customization you need (managing |
||||||
|
template engine configuration for example). |
||||||
|
|
||||||
|
[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, |
||||||
|
https://github.com/spring-projects/spring-framework/tree/master/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/script[java], and |
||||||
|
https://github.com/spring-projects/spring-framework/tree/master/spring-webflux/src/test/resources/org/springframework/web/reactive/result/view/script[resources], |
||||||
|
for more configuration examples. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[[webflux-view-httpmessagewriter]] |
||||||
|
== JSON, XML |
||||||
|
[.small]#<<web.adoc#mvc-view-jackson,Same in Spring MVC>># |
||||||
|
|
||||||
|
For <<webflux-multiple-representations>> 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 this Spring WebFlux |
||||||
|
provides the `HttpMessageWriterView` that can be used to plug in any of the available |
||||||
|
<<webflux-codecs>> from `spring-web` such as `Jackson2JsonEncoder`, |
||||||
|
`Jackson2SmileEncoder`, or `Jaxb2XmlEncoder`. |
||||||
|
|
||||||
|
Unlike other view technologies, `HttpMessageWriterView` does not require a `ViewResolver`, |
||||||
|
but instead is <<webflux-config-view-resolvers,configured>> as a default view. You can |
||||||
|
configure one more such default views, wrapping different ``HttpMessageWriter``'s or |
||||||
|
``Encoder``'s. The one that matches the requested content type is used at runtime. |
||||||
|
|
||||||
|
In most cases a model will contain multiple attributes. In order to determine which one |
||||||
|
to serialize, `HttpMessageWriterView` can be configured with the name of the model |
||||||
|
attribute to use render, of if the model contains only one attribute, it will be used. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in new issue