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("abc@gmail.com", 0)); //4. Test List<Object> List<User> list = new ArrayList<>(); list.add(new User("aaa@gmail.com", 1)); list.add(new User("bbb@yahoo.com", 2)); list.add(new User("ccc@hotmail.com", 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 with Java.from().
- For List<User>, convert it into JSON formatted string with Jackson, and convert it to a JS object via JSON.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/
References
- Mustache – Logic-less templates
- Mustache – Javascript
- Spring IO – View Template
- Isomorphic templating with Spring Boot, Nashorn and React
- Java 8 Nashorn tutorial
- Spring React example
- Yet another Spring Mustache solution
- Isomorphic JavaScript
From:一号门
COMMENTS