Spring MVC + Mustache JS template example
In this tutorial, we will show you a Spring MVC + Mustache JS template web application example.
Technologies used :
- Spring 4.2.4.RELEASE
- mustache.js
- JDK 1.8.0_66
- Jackson 2.7
- Eclipse 4.3
- Tomcat 8
- Maven 3
- Bootstrap 3
This solution is still working, but recommend this simple Spring Boot + JMustache solution.
In Spring, we can use ScriptTemplateConfigurer
to integrate most of the JS templating library, for example Mustache, Handlebars or React. Refer to this official Spring script template documentation.
P.S ScriptTemplateConfigurer
has been supported since Spring 4.2
This example will run “Mustache JS” on the server side, using the built in Java 8 Nashorn Javascript engine. An isomorphic javascript example.
1. Project Structure
A standard Maven folder structure.
2. pom.xml
A Maven pom.xml
file to declare dependencies and some useful plugins.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mkyong</groupId>
<artifactId>spring-mvc-mustache</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>spring mvc mustache</name>
<properties>
<jdk.version>1.8</jdk.version>
<spring.version>4.2.4.RELEASE</spring.version>
<servletapi.version>3.1.0</servletapi.version>
<logback.version>1.1.3</logback.version>
<jcl.slf4j.version>1.7.12</jcl.slf4j.version>
<jackson.version>2.7.0</jackson.version>
</properties>
<dependencies>
<!-- Spring and exclude commons log -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Add slf4j Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${jcl.slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- compile only, deployed container will provide this -->
<!-- no web.xml project need this to start app -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servletapi.version}</version>
<scope>provided</scope>
</dependency>
<!-- JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.2.11.v20150529</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<webApp>
<contextPath>/spring</contextPath>
</webApp>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
<wtpversion>2.0</wtpversion>
<wtpContextName>spring</wtpContextName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
3. Integrate Spring + Mustache JS
To integrate Mustache script template as the Spring view template, configure both ScriptTemplateConfigurer
and ViewResolver
. See comment for self-explanatory.
package com.mkyong.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.script.ScriptTemplateConfigurer;
import org.springframework.web.servlet.view.script.ScriptTemplateViewResolver;
@EnableWebMvc
@Configuration
@ComponentScan({ "com.mkyong.web.controller" })
public class SpringWebConfig extends WebMvcConfigurerAdapter {
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
//1. Nashorn jdk8 script engine.
configurer.setEngineName("nashorn");
//2. Add mustache.min.js and custom render.js to Nashorn
configurer.setScripts("/static/js/mustache.min.js", "/static/js/render.js");
//3. Ask Nashorn to run this function "render()"
configurer.setRenderFunction("render");
return configurer;
}
//Define where is Mustache template, in classpath level.
// If view "hello" is returned, Mustache temple will be '/static/templates/hello.html'
@Bean
public ViewResolver viewResolver() {
ScriptTemplateViewResolver viewResolver = new ScriptTemplateViewResolver();
viewResolver.setPrefix("/static/templates/");
viewResolver.setSuffix(".html");
return viewResolver;
}
//add static resources like js or css
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
4. Spring Controller + Mustache Template
4.1 A Spring controller to pass data to the Mustache template.
package com.mkyong.web.controller;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mkyong.web.model.User;
@Controller
public class HelloController {
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
private static final ObjectMapper om = new ObjectMapper();
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView printWelcome(HttpServletRequest request) {
ModelAndView result = new ModelAndView();
result.addObject("resources", request.getContextPath() + "/resources");
result.addObject("logo", "mkyong.com");
result.addObject("title", "Spring MVC + Mustache template");
result.addObject("jumbo-title", "Spring MVC + Mustache template");
result.addObject("jumbo-desc", "Maven + Spring MVC + Mustache JS, ScriptTemplate example.");
//1. Test data type
result.addObject("id", 100);
result.addObject("username", "mkyong");
//2. Test List
result.addObject("scriptTemplates", getScriptTemplate());
//3. Test Object
result.addObject("user", new User("[email protected]", 0));
//4. Test List<Object>
List<User> list = new ArrayList<>();
list.add(new User("[email protected]", 1));
list.add(new User("[email protected]", 2));
list.add(new User("[email protected]", 3));
result.addObject("users_json", convertObjectToJson(list));
result.addObject("users", list);
result.setViewName("hello");
return result;
}
private List<String> getScriptTemplate() {
List<String> scriptTemplates = new ArrayList<>();
scriptTemplates.add("Handlebars");
scriptTemplates.add("Mustache");
scriptTemplates.add("React");
scriptTemplates.add("EJS");
scriptTemplates.add("ERB");
scriptTemplates.add("String templates");
return scriptTemplates;
}
//Jackson2 - Convert Java Object to JSON format
public static String convertObjectToJson(Object obj) {
String result = "";
try {
result = om.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (JsonProcessingException e) {
logger.error("Error In JSON conversion : {}", e);
}
return result;
}
}
4.2 A Mustache template file to display the data from the above Spring controller.
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{title}}</title>
<link href="{{{resources}}}/core/css/bootstrap.min.css" rel="stylesheet">
<link href="{{{resources}}}/core/css/bootstrap-theme.min.css" rel="stylesheet">
<link href="{{{resources}}}/core/css/hello.css" rel="stylesheet">
</head>
<body role="document">
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">{{logo}}</a>
</div>
</div>
</nav>
<div class="container theme-showcase" role="main">
<div class="jumbotron">
<h1>{{jumbo-title}}</h1>
<p>{{jumbo-desc}}</p>
</div>
<div class="row">
<div class="col-sm-6">
<div class="page-header">
<h1>1. Data Types</h1>
</div>
<p>
<div>
{{#id}}
Hello {{username}}, id : {{.}}
{{/id}}
</div>
</p>
</div>
<div class="col-sm-6">
<div class="page-header">
<h1>2. List</h1>
</div>
<p>
<pre>{{scriptTemplates}}</pre>
<ol>
{{#scriptTemplates}}
<li>{{.}}</li>
{{/scriptTemplates}}
</ol>
</p>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="page-header">
<h1>3. User Object</h1>
</div>
<p>
<div>
<pre>{{user}}</pre>
<ol>
{{#user}}
<li>email- {{user.email}}</li>
<li>loginFailed- {{user.loginFailed}}</li>
{{/user}}
</ol>
</div>
</p>
</div>
<div class="col-sm-6">
<div class="page-header">
<h1>4. List of User Objects (JSON)</h1>
</div>
<p>
<pre>{{user_json}}</pre>
{{#users_json}}
<ol>
<li>email- {{email}}</li>
<li>loginFailed- {{loginFailed}}</li>
</ol>
{{/users_json}}
</p>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="page-header">
<h1>5. List of User Objects (RAW)</h1>
</div>
<p>
No idea how to loop it...
<pre>{{users}}</pre>
{{#users}}
<ol>
<li>email- {{email}}</li>
<li>loginFailed- {{loginFailed}}</li>
</ol>
{{/users}}
</p>
</div>
<div class="col-sm-6">
<div class="page-header">
<h1></h1>
</div>
<p>
</p>
</div>
</div>
</div>
<div class="container">
<hr>
<footer>
<p>© Mkyong.com 2016</p>
</footer>
</div>
<script type="text/javascript" src="{{{resources}}}/core/js/hello.js"></script>
<script type="text/javascript" src="{{{resources}}}/core/js/jquery-1.12.0.min.js"></script>
<script type="text/javascript" src="{{{resources}}}/core/js/bootstrap.min.js"></script>
</body>
</html>
5. Javascript ‘render.js’
When a view is returned, Spring will run this function render(template, model, url)
. But, not all Java objects or data types are supported in Javascript, conversion is required, especially the List<Object>
.
Summary :
- For primitive data type, there is no need for conversion.
- For
java.lang.Iterable
, convert it withJava.from()
. - For
List<User>
, convert it into JSON formatted string withJackson
, and convert it to a JS object viaJSON.parse
. This trick should work in any Java object <--> JavaScript conversion.
/**
* Bindings to use mustache.js with Nashorn.
*/
/**
* String templates: the Mustache template content. e.g hello.html
* Map model: the model in controller
* String url: the templates url (since 4.2.2)
*/
function render(template, model, url) {
return Mustache.render(template, toJsonObject(model));
}
// Some Java objects may not support in JS directly, need conversion.
function toJsonObject(model) {
var o = {};
for (var k in model) {
//Convert Object String to Javascript JSON
if (k.indexOf("_json") > -1) {
o[k] = JSON.parse(model[k]);
continue;
}
// Convert Iterable like List to real JSON array
if (model[k] instanceof Java.type("java.lang.Iterable")) {
o[k] = Java.from(model[k]);
}
else {
o[k] = model[k];
}
}
return o;
}
6. Misc Classes
6.1 Create a WebInitializer
class to create a no web.xml
web application.
package com.mkyong.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import com.mkyong.config.SpringWebConfig;
public class MyWebInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { SpringWebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
}
6.2 User Object.
package com.mkyong.web.model;
public class User {
String email;
int loginFailed;
public User(String email, int loginFailed) {
this.email = email;
this.loginFailed = loginFailed;
}
//setters and getters
}
7. Demo
7.1 http://localhost:8080/spring/
7.2 http://localhost:8080/spring/
8. How to run this project?
8.1 Clone the source code from Github.
$ git clone https://github.com/mkyong/spring-mvc-mustache-js-template
8.2 Run the embedded Jetty container.
$ mvn jetty:run
8.3 Visit URL : http://localhost:8080/spring/
Hi …Thanks for this wonderful post. I was looking for js code here which used in html. Can you give me a example how I have to configure for js..?
Can you please provide example for using function toJsonObject(model) from render.js
How to get this folder structure in Eclipse-STS.
nice