Using Thymeleaf with Spring MVC
A previous post introduced the features of the Thymeleaf template engine in a standalone environment. Even if a standalone use of Thymeleaf can make sense – e.g. for short document generation or view testing – developers usually use the template engine in a web environment. This post covers how to integrate Thymeleaf with one of the most popular Java web frameworks, Spring MVC. We’ll see also how to test the controller and the view with Spring MVC test framework.
Thymeleaf infrastructure in Spring MVC
Thymeleaf comes with out-of-the-box support for Spring MVC. We just have to declare ThymeleafViewResolver
bean, which acts as a bridge between Thymeleaf’s template engine and Spring MVC’s view rendering mechanism. We’re using Spring Java configuration, but a traditional XML configuration would work as well:
@Configuration public class ContactConfiguration { @Bean public ViewResolver viewResolver() { ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); templateResolver.setTemplateMode("XHTML"); templateResolver.setPrefix("views/"); templateResolver.setSuffix(".html"); SpringTemplateEngine engine = new SpringTemplateEngine(); engine.setTemplateResolver(templateResolver); ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); viewResolver.setTemplateEngine(engine); return viewResolver; } }
The ThymeleafViewResolver
needs a SpringTemplateEngine
instance, whose setup is the same as in the standalone setup. Note we’re using a Thymeleaf template resolver that resolves templates against the classpath.
The Spring MVC controller
Our controller is rather simple: it retrieves a list of Java objects from a backend service and put the list into the model. The list will then by available for display in the view. Here’s the controller:
@Controller public class ContactController { @Autowired private ContactService contactService; @RequestMapping("/contacts") public String contacts(Model model) { model.addAttribute("contacts",contactService.getContacts()); return "contacts"; } }
We want Spring MVC to pick up our controller and to map it against the /contacts
URL. To do so, we add component scanning to the configuration Java class:
@Configuration @ComponentScan(basePackageClasses = ContactController.class) public class ContactConfiguration { ... }
OK, time to work on the view.
The template
Our previous Thymeleaf post already covered the basics of the templating language. Let’s build on these basics and reuse features like conditionals, iteration, and variable substitution:
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring3-3.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <title>Contacts</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <table th:if="${not #lists.isEmpty(contacts)}"> <tr> <th>Firstname</th> <th>Lastname</th> </tr> <tr th:each="contact : ${contacts}"> <td th:text="${contact.lastname}">The first name</td> <td th:text="${contact.firstname}">The last name</td> </tr> </table> </body> </html>
That’s it, we’re done with the setup, the controller, and the view! In a traditional post, we would deploy the Spring MVC application in a web container to test the controller and the view rendering, but this isn’t a traditional post! This other article covered how to do out-of-container testing with Spring MVC, so let’s use this excellent tool for testing our Spring MVC and Thymeleaf integration.
The test
We’re using JUnit 4 and Spring’s TestContext framework to test our web application. The test needs some extra annotations, an initialization sequence to start the mock Spring MVC instance, and the Spring configuration. All of this fits in the test class:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @WebAppConfiguration public class SpringMvcTest { @Autowired WebApplicationContext ctx; MockMvc mvc; @Before public void setUp() throws Exception { mvc = webAppContextSetup(ctx).build(); } @Test public void contact() throws Exception { } @Configuration @import(ContactConfiguration.class) public static class TestConfiguration { @Bean public ContactService contactService() { return new StubContactService(); } } }
There are 3 parts in the test class:
The test setup, with the @RunWith
, @ContextConfiguration
, @WebAppConfiguration
annotations, and the creation of the MockMvc
object.
The test method, empty for now.
The Spring configuration, as a @Configuration
static inner class. Note this class imports our main, application-specific ContactConfiguration
class and register a stub for the backend service.
This is enough to check the setup of our web application: you can launch the test and should get a green bar.
Let’s simulate a request on our controller and check the content of the rendered view:
@Test public void contact() throws Exception { mvc.perform(get("/contacts")) .andDo(print()); }
By launching the test, you get something like the following on the console:
MockHttpServletRequest: HTTP Method = GET Request URI = /contacts Parameters = {} Headers = {} Handler: Type = com.zenika.web.ContactController Async: Was async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = contacts View = null Attribute = contacts value = [com.zenika.Contact@605b28c9, com.zenika.Contact@d2d58b] FlashMap: MockHttpServletResponse: Status = 200 Error message = null Headers = {Content-Type=} Content type = text/html;charset=ISO-8859-1 Body = <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Contacts</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <table> <tr> <th rowspan="1" colspan="1">Firstname</th> <th rowspan="1" colspan="1">Lastname</th> </tr> <tr> <td rowspan="1" colspan="1">Doe</td> <td rowspan="1" colspan="1">John</td> </tr><tr> <td rowspan="1" colspan="1">Doe</td> <td rowspan="1" colspan="1">Jane</td> </tr> </table> </body> </html> Forwarded URL = null Redirected URL = null Cookies = []
Looks like our stub backend service returns the Doe couple. We can add the following expectations to our test:
@Test public void contact() throws Exception { mvc.perform(get("/contacts")) .andExpect(status().isOk()) .andExpect(content().string(containsString("John"))) .andExpect(content().string(containsString("Jane"))); }
That’s it, we successfully integrated Thymeleaf with Spring MVC and tested the integration without even starting a web container.
Conclusion
Thymeleaf provides first-class support for Spring MVC. This article covered the basic integration, thanks to the ThymeleafViewResolver
. By using Thymeleaf in your Spring MVC application, you get all the features of this templating engine: powerful templating language, no JSP compiler, fast rendering thanks to template caching, etc. Note the integration with Spring MVC doesn’t stop here: Thymeleaf also supports form rendering and binding, to write interactive web application.
Source code
This doesn’t seem to work. The unit test runs, but the project doesn’t run inside a container like JBoss. The service is not annotated as a component, for instance; nor is it clear how the viewResolver is wired in.