Spring Framework имеет встроенную интеграцию для использования Spring MVC с любой библиотекой шаблонизации, которая может выполняться на основе механизма выполнения скриптов по спецификации JSR-223 в Java. Мы протестировали следующие библиотеки шаблонизации на различных механизмах выполнения скриптов:

Библиотека скриптов Механизм выполнения скриптов

Handlebars

Nashorn

Mustache

Nashorn

React

Nashorn

EJS

Nashorn

ERB

JRuby

String templates

Jython

Kotlin Script templating

Kotlin

Основное правило для интеграции любого другого механизма выполнения скриптов заключается в том, что он должен реализовать интерфейсы ScriptEngine и Invocable.

Требования

Необходимо обеспечить наличие механизма выполнения скриптов в своем classpath, детали которого зависят от механизма выполнения скриптов:

  • Механизм обработки JavaScript под названием Nashorn поставляется вместе с Java 8+. Настоятельно рекомендуется использовать последний доступный выпуск обновления.

  • JRuby необходимо добавить в качестве зависимости для обеспечения поддержки языка Ruby.

  • Jython необходимо добавить в качестве зависимости для обеспечения поддержки языка Python.

  • Для обеспечения поддержки скриптов Kotlin следует добавить зависимость org.jetbrains.kotlin:kotlin-script-util и файл META-INF/services/javax.script.ScriptEngineFactory, содержащий строку org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory. Более подробную информацию см. в этом примере.

Вам необходимо иметь библиотеку шаблонизации скриптов. Один из способов сделать это для JavaScript - WebJars.

Шаблоны скриптов

Вы можете объявить бин ScriptTemplateConfigurer, чтобы задать, какой механизм выполнения скриптов использовать, какие файлы скриптов загружать, какую функцию вызывать для визуализации шаблонов и так далее. В следующем примере используются шаблоны Mustache и механизм обработки скриптов JavaScript под названием Nashorn:

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @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
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.scriptTemplate()
    }
    @Bean
    fun configurer() = ScriptTemplateConfigurer().apply {
        engineName = "nashorn"
        setScripts("mustache.js")
        renderObject = "Mustache"
        renderFunction = "render"
    }
}

В следующем примере показан такой же способ организации в XML:

<mvc:annotation-driven/>
<mvc:view-resolvers>
    <mvc:script-template/>
</mvc:view-resolvers>
<mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render">
    <mvc:script location="mustache.js"/>
</mvc:script-template-configurer>

Контроллер не будет отличаться для конфигураций на Java и XML, как показано в следующем примере:

Java
@Controller
public class SampleController {
    @GetMapping("/sample")
    public String test(Model model) {
        model.addAttribute("title", "Sample title");
        model.addAttribute("body", "Sample body");
        return "template";
    }
}
Kotlin
@Controller
class SampleController {
    @GetMapping("/sample")
    fun test(model: Model): String {
        model["title"] = "Sample title"
        model["body"] = "Sample body"
        return "template"
    }
}

В следующем примере показан шаблон Mustache:

<html>
    <head>
        <title>{{title}}</title>
    </head>
    <body>
        <p>{{body}}</p>
    </body>
</html>

Функция визуализации вызывается со следующими параметрами:

  • String template: Содержание шаблона

  • Map model: Модель представления

  • RenderingContext renderingContext: RenderingContext, который предоставляет доступ к контексту приложения, региональным настройкам, загрузчику шаблонов и URL-адресу (начиная с версии 5.0).

Mustache.render() изначально совместим с этой сигнатурой, поэтому можно вызывать его напрямую.

Если ваша технология шаблонизации требует некоторой настройки, то можно передать скрипт, который реализует кастомную функцию визуализации. Например, Handlerbars требует компиляции шаблонов перед их использованием и требует полизаполнение для эмуляции некоторых возможностей браузера, которые недоступны в механизме выполнения скриптов на стороне сервера.

В следующем примере показано, как это сделать:

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @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
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
    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
    }
}
Установка свойства sharedEngine в false необходима при использовании небезопасных для потоков механизмов выполнения скриптов с библиотеками шаблонов, не рассчитанными на параллелизм, например, Handlebars или React, работающие на Nashorn. В этом случае требуется обновление Java SE 8 update 60 из-за этого бага, но обычно в любом случае рекомендуется использовать последний выпуск патча Java SE.

polyfill.js определяет только объект window, необходимый Handlebars для правильной работы, следующим образом:

var window = {};

Эта базовая реализация render.js компилирует шаблон перед его использованием. Реализация, пригодная к производственному использованию, также должна хранить любые повторно используемые кэшированные шаблоны или предварительно скомпилированные шаблоны. Это можно сделать на стороне скрипта (и обработать любую необходимую вам настройку – например, управление конфигурацией шаблонизатора). В следующем примере показано, как это сделать:

function render(template, model) {
    var compiledTemplate = Handlebars.compile(template);
    return compiledTemplate(model);
}

Ознакомьтесь с модульными тестами Spring Framework, Java и ресурсами, чтобы просмотреть больше примеров конфигурации.