About Xavier Padro

Xavier is a software developer working in a consulting firm based in Barcelona. He is specialized in web application development with experience in both frontend and backend. He is interested in everything related to Java and the Spring framework

Thymeleaf integration with Spring (Part 1)

1.Introduction

This article is focused on how Thymeleaf can be integrated with the Spring framework. This will let our MVC web application take advantage of Thymeleaf HTML5 template engine without losing any of the Spring features. The data layer uses Spring Data to interact with a mongoDB database.

The example consists in a Hotel’s single page web application from where we can send two different requests:
 
 

  • Insert a new guest: A synchronous request that shows how Thymeleaf is integrated with Spring’s form backing beans.
  • List guests: An asynchronous request that shows how to handle fragment rendering with AJAX.

This tutorial expects you to know the basics of Thymeleaf. If not, you should first read this article.

Here’s an example of the application flow:

hotelFlow

This example is based on Thymeleaf 2.1 and Spring 4 versions.

  • The source code can be found at github.

2.Configuration

This tutorial takes the JavaConfig approach to configure the required beans. This means xml configuration files are no longer necessary.

web.xml

Since we want to use JavaConfig, we need to specify AnnotationConfigWebApplicationContext as the class that will configure the Spring container. If we don’t specify it, it will use XmlWebApplicationContext by default.

When defining where the configuration files are located, we can specify classes or packages. Here, I’m indicating my configuration class.

<!-- Bootstrap the root context -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext -->
<context-param>
    <param-name>contextClass</param-name>
    <param-value>
        org.springframework.web.context.support.AnnotationConfigWebApplicationContext
    </param-value>
</context-param>

<!-- @Configuration classes or package -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>xpadro.thymeleaf.configuration.WebAppConfiguration</param-value>
</context-param>

<!-- Spring servlet -->
<servlet>
    <servlet-name>springServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>springServlet</servlet-name>
    <url-pattern>/spring/*</url-pattern>
</servlet-mapping>

Spring Configuration

My configuration is split in two classes: thymeleaf-spring integration ( WebAppConfiguration class) and mongoDB configuration (MongoDBConfiguration class).

WebAppConfiguration.java

@EnableWebMvc
@Configuration
@ComponentScan("xpadro.thymeleaf")
@Import(MongoDBConfiguration.class)
public class WebAppConfiguration extends WebMvcConfigurerAdapter {
    @Bean
    @Description("Thymeleaf template resolver serving HTML 5")
    public ServletContextTemplateResolver templateResolver() {
        ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
        templateResolver.setPrefix("/WEB-INF/html/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode("HTML5");
        
        return templateResolver;
    }
    
    @Bean
    @Description("Thymeleaf template engine with Spring integration")
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver());
        
        return templateEngine;
    }
    
    @Bean
    @Description("Thymeleaf view resolver")
    public ThymeleafViewResolver viewResolver() {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine());
        
        return viewResolver;
    }
    
    @Bean
    @Description("Spring message resolver")
    public ResourceBundleMessageSource messageSource() {  
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();  
        messageSource.setBasename("i18n/messages");  
        
        return messageSource;  
    }
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/WEB-INF/resources/");
    }
}

Things to highlight from looking at the above code:

  • @EnableWebMvc: This enables Spring MVC annotations like @RequestMapping. This would be the same as the xml namespace <mvc:annotation-driven />
  • @ComponentScan(“xpadro.thymeleaf”): Activates component scanning in the xpadro.thymeleaf package and subpackages. Classes annotated with @Component and related annotations will be registered as beans.
  • We are registering three beans which are necessary to configure Thymeleaf and integrate it with the Spring framework.
    • template resolver: Resolves template names and delegates them to a servlet context resource resolver.
    • template engine: Integrates with Spring framework, establishing the Spring specific dialect as the default dialect.
    • view resolver: Thymeleaf implementation of the Spring MVC view resolver interface in order to resolve Thymeleaf views.

MongoDBConfiguration.java

@Configuration
@EnableMongoRepositories("xpadro.thymeleaf.repository")
public class MongoDBConfiguration extends AbstractMongoConfiguration {
    @Override
    protected String getDatabaseName() {
        return "hotel-db";
    }
    
    @Override
    public Mongo mongo() throws Exception {
        return new Mongo();
    }
}

This class extends AbstracMongoConfiguration, which defines mongoFactory and mongoTemplate beans.

The @EnableMongoRepositories will scan the specified package in order to find interfaces extending MongoRepository. Then, it will create a bean for each one. We will see this later, at the data access layer section.

3.Thymeleaf – Spring MVC Integration

HotelController

The controller is responsible for accessing the service layer, construct the view model from the result and return a view. With the configuration that we set in the previous section, now MVC Controllers will be able to return a view Id that will be resolved as a Thymeleaf view.

Below we can see a fragment of the controller where it handles the initial request (http://localhost:8080/th-spring-integration/spring/home):

@Controller
public class HotelController {
    @Autowired
    private HotelService hotelService;
    
    @ModelAttribute("guest")
    public Guest prepareGuestModel() {
        return new Guest();
    }
    
    @ModelAttribute("hotelData")
    public HotelData prepareHotelDataModel() {
        return hotelService.getHotelData();
    }
    
    @RequestMapping(value = "/home", method = RequestMethod.GET)
    public String showHome(Model model) {
        prepareHotelDataModel();
        prepareGuestModel();
        
        return "home";
    }
    
    ...
}

A typical MVC Controller that returns a “home” view id. Thymeleaf template resolver will look for a template named “home.html” which is located in /WEB-INF/html/ folder, as indicated in the configuration. Additionally, a view attribute named “hotelData” will be exposed to the Thymeleaf view, containing hotel information that needs to be displayed on the initial view.

This fragment of the home view shows how it accesses some of the properties of the view attribute by using Spring Expression Language (Spring EL):

<span th:text="${hotelData.name}">Hotel name</span><br />
<span th:text="${hotelData.address}">Hotel address</span><br />

Another nice feature is that Thymeleaf will be able to resolve Spring managed message properties, which have been configured through the MessageSource interface.

<h3 th:text="#{hotel.information}">Hotel Information</h3>

Error handling

Trying to add a new user will raise an exception if a user with the same id already exists. The exception will be handled and the home view will be rendered with an error message.

Since we only have one controller, there’s no need to use @ControllerAdvice. We will instead use a @ExceptionHandler annotated method. You can notice that we are returning an internationalized message as the error message:

@ExceptionHandler({GuestFoundException.class})
public ModelAndView handleDatabaseError(GuestFoundException e) {
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("home");
    modelAndView.addObject("errorMessage", "error.user.exist");
    modelAndView.addObject("guest", prepareGuestModel());
    modelAndView.addObject("hotelData", prepareHotelDataModel());
    
    return modelAndView;
}

Thymeleaf will resolve the view attribute with ${} and then it will resolve the message #{}:

<span class="messageContainer" th:unless="${#strings.isEmpty(errorMessage)}" th:text="#{${errorMessage}}"></span>

The th:unless Thymeleaf attribute will only render the span element if an error message has been returned.

hotelError

4.The Service layer

The service layer accesses the data access layer and adds some business logic.

@Service("hotelServiceImpl")
public class HotelServiceImpl implements HotelService {
    @Autowired
    HotelRepository hotelRepository;
    
    @Override
    public List<Guest> getGuestsList() {
        return hotelRepository.findAll();
    }
    
    @Override
    public List<Guest> getGuestsList(String surname) {
        return hotelRepository.findGuestsBySurname(surname);
    }
    
    @Override
    public void insertNewGuest(Guest newGuest) {
        if (hotelRepository.exists(newGuest.getId())) {
            throw new GuestFoundException();
        }
        
        hotelRepository.save(newGuest);
    }
}

5.The Data Access layer

The HotelRepository extends the Spring Data class MongoRepository.

public interface HotelRepository extends MongoRepository<Guest, Long> {
    @Query("{ 'surname' : ?0 }")
    List<Guest> findGuestsBySurname(String surname);
}

This is just an interface, we won’t implement it. If you remember the configuration class, we added the following annotation:

@EnableMongoRepositories("xpadro.thymeleaf.repository")

Since this is the package where the repository is located, Spring will create a bean and inject a mongoTemplate to it. Extending this interface provides us with generic CRUD operations. If you need additional operations, you can add them with the @Query annotation (see code above).

6.Conclusion

We have configured Thymeleaf to resolve views in a Spring managed web application. This allows the view to access to Spring Expression Language and message resolving. The next part of this tutorial is going to show how forms are linked to Spring form backing beans and how we can reload fragments by sending an AJAX request.
 

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

2 Responses to "Thymeleaf integration with Spring (Part 1)"

  1. Essi says:

    Hi, nice and clear. In repository you could name the method findBySurname and let spring derive query from method name.

  2. Hi Essi,

    Thank you for pointing that out. I will add a clarification in my post.

Leave a Reply


+ 3 = seven



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close