In this post we will configure a Spring
project to use Thymeleaf along with Thymeleaf Layout Dialect
(Thymeleaf Layout Dialect) We will use Bootstrap
front end libraries to demonstrate responsive front end development with Spring and Thymeleaf.
Following technology stack is used.
- Java 1.7
- Spring 4.0.6 RELEASE
- thymeleaf-spring4 3.0.1RELEASE
-
thymeleaf-layout-dialect 2.0.4RELEASE
Let's Begin
- Project Structure
2. Dependency Management [pom.xml]
<?xml version="1.0" encoding="UTF-8"?> <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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>info.balloons</groupId> <artifactId>SpringThymeleaf</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>SpringThymeleaf</name> <url>http://maven.apache.org</url> <properties> <java.version>1.7</java.version> <spring.version>4.0.6.RELEASE</spring.version> <cglib.version>2.2.2</cglib.version> </properties> <dependencies> <!-- Spring core & mvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- CGLib for @Configuration --> <dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>${cglib.version}</version> <scope>runtime</scope> </dependency> <!-- Thymeleaf --> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf</artifactId> <version>3.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring4</artifactId> <version>3.0.1.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect --> <dependency> <groupId>nz.net.ultraq.thymeleaf</groupId> <artifactId>thymeleaf-layout-dialect</artifactId> <version>2.0.4</version> </dependency> <!-- Servlet Spec --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> </dependencies> <repositories> <repository> <id>springsource-milestones</id> <name>SpringSource Milestones Proxy</name> <url>https://oss.sonatype.org/content/repositories/springsource-milestones</url> </repository> </repositories> <build> <finalName>SpringThymeleaf</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> </plugins> </build> </project>
3. Spring MVC and Thymeleaf Configuration [ThymeleafConfig.java]
package info.balloons.SpringThymeleaf.config;
import nz.net.ultraq.thymeleaf.LayoutDialect;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
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.thymeleaf.TemplateEngine;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
@Configuration
@ComponentScan(basePackages="info.balloons.studentsProject")
@EnableWebMvc
public class ThymeleafConfig extends WebMvcConfigurerAdapter implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine((SpringTemplateEngine) templateEngine());
resolver.setCharacterEncoding("UTF-8");
return resolver;
}
@Bean
public TemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setEnableSpringELCompiler(true);
engine.setTemplateResolver(templateResolver());
engine.addDialect(new LayoutDialect());
return engine;
}
private ITemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode("HTML5");
return resolver;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
@Configuration
denotes that this class contains one or more methods annotated with @Bean
annotation, that produces Beans that are in turn manageable by the Spring Container. @ComponentScan
is similar to context:component-scan base-package="..."
that gives information to Spring about where to look for beans / classes. @EnableWebMvc
is similar to mvc:annotation-driven
in XML. It enables mapping of incoming request to Controller methods annotated with @RequestMapping
annotation. viewResolver
helps us to work with views in Spring. We are using ThymeleafViewResolver()
since we are interested in working with Thymeleaf. SpringTemplateEngine
provides is a subClass of TemplateEngine
, that is used to alter to settings to Thymeleaf Settings. This is where we set thymeleaf to use dialect via engine.addDialect(new LayoutDialect());
3. Controller [HomeController.java]
package info.balloons.SpringThymeleaf.controller; import java.io.IOException; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class HomeController { @RequestMapping(value="/") public ModelAndView test(HttpServletResponse response) throws IOException{ return new ModelAndView("home"); } @RequestMapping(value="/demoPage1") public ModelAndView demoPage1(HttpServletResponse response) throws IOException{ return new ModelAndView("demoPage1"); } @RequestMapping(value="/demoPage2") public ModelAndView demoPage2(HttpServletResponse response) throws IOException{ return new ModelAndView("demoPage2"); } }
@Controller
annotation is used above the Class to denote a Spring MVC Controller and @RequestMapping
annotation is used to map url to particular controller method. We have created there sample pages to demonstrate the workings.
4. Layout Template [bootstraplayout.html]
<!DOCTYPE html> <html lang="en"> <head> <th:block th:replace="layout/partials/head"></th:block> </head> <body> <th:block th:replace="layout/partials/nav"></th:block> <!-- Page Content --> <div layout:fragment="content" /> <!-- /.container --> <th:block th:replace="layout/partials/footer"></th:block> </body> </html>
This is our basic bootstrap layout which will be extended by all other view files in the project to have the same basic structure of header , navigation and static asset file imports.
This block of code is used to substitute the content at this place with the file mentioned in th:replace. We have created separate file for head, nav and footer.
layout:fragment="content"
this piece of code will replace the content of file which this layout as its decorator. This will make more sense once you see the file that uses this layout template.
5. Layout partial files [ head.html, nav.html, footer.html]
<meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content=""> <title>Spring Thymeleaf Dialect Configuration</title> <!-- Bootstrap Core CSS --> <link th:href="@{/resources/css/bootstrap.min.css}" rel="stylesheet"> <!-- Custom CSS --> <link th:href="@{/resources/css/logo-nav.css}" rel="stylesheet"> <script th:inline="javascript"> var contextRoot = /*[[@{/}]]*/ ''; </script> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script> <![endif]-->
The above head.html
contains the necessary bootstrap css imports.
<!-- Navigation --> <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="container"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" th:href="@{/}"> Spring Thymeleaf </a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li> <a th:href="@{/demoPage1}">Demo Page 1</a> </li> <li> <a th:href="@{/demoPage2}">Demo Page 2</a> </li> </ul> </div> <!-- /.navbar-collapse --> </div> <!-- /.container --> </nav>
The above nav.html
file contains the basic bootstrap nav menu structure.
<!-- jQuery --> <script th:src="@{/resources/js/jquery.js}"></script> <!-- Bootstrap Core JavaScript --> <script th:src="@{/resources/js/bootstrap.min.js}"></script> <!-- Bootstrap Core JavaScript --> <script th:src="@{/resources/js/custom.js}"></script>
The above footer.html
file contains the basic bootstrap js includes which will be inserted at bottom of template just above the body tag closure.
6. File that extends the template [home.html]
<th:block layout:decorator="layout/bootstraplayout" layout:fragment="content"> <!-- Page Content --> <div class="container" id="homePage"> <div class="row"> <div class="col-lg-12"> <h2>This is Home Page</h2> <div class="well"> I am inside a bootstrap well. </div> </div> </div> </div> <!-- /.container --> </th:block>
layout:decorator="layout/bootstraplayout"
is used to extend the layout we created. We have created two more similar view files in the project demo1 and demo2 to demonstrate how a single template layout file can be used to extend to multiple views.