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=[text/html;charset=ISO-8859-1]}
        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


Commentaires

1. Le mercredi 13 novembre 2013, 12:36 par Biffo

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.

Fil des commentaires de ce billet

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.