in spring thymeleaf moteur de template ~ de lecture.

Thymeleaf : mise en place dans un projet Spring Boot

Dans la famille "moteur de template côté serveur" je vous présente Thymeleaf, mon chouchou du moment après avoir testé principalement JSP et FreeMarker.

Thymeleaf

Thymeleaf a la particularité d'avoir été pensé "Templates Natural", c'est à dire que les templates HTML écrits dans ce langage s'affichent correctement en les ouvrant dans votre navigateur préféré en mode "offline".
L'intégrateur Web et le développeur peuvent à présent travailler en même temps sur les mêmes pages HTML sans que l'un soit (trop ?) perturbé par l'autre.

Configuration

Sa mise en place dans Spring Framework est quasi "native", je vous épargne bien entendu la configuration XML de la chose :

@Configuration
public class ThymeleafConfiguration {

    private final Logger log = LoggerFactory.getLogger(ThymeleafConfiguration.class);

    @Autowired
    private Environment env;

    @Bean
    public TemplateResolver templateResolver() {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setPrefix("classpath:/templates/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode("HTML5");
        resolver.setCharacterEncoding(CharEncoding.UTF_8);
        if (env.acceptsProfiles(Constants.SPRING_PROFILE_DEVELOPMENT)) {
            resolver.setCacheable(false); // default is true (for prod)
        } else {
            log.info("Cache des templates Thymeleaf activé");
        }
        return resolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setTemplateResolver(templateResolver());
        engine.addDialect(new ConditionalCommentsDialect());
        engine.addDialect(new LayoutDialect());
        if (env.acceptsProfiles(Constants.SPRING_PROFILE_PRODUCTION)) {
            log.info("Cache manager activé");
            engine.setCacheManager(thymeleafCacheManager());
        }
        return engine;
    }

    @Bean
    public ThymeleafViewResolver thymeleafViewResolver() {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine());
        resolver.setCharacterEncoding(CharEncoding.UTF_8);
        return resolver;
    }

    @Bean
    public StandardCacheManager thymeleafCacheManager() {
        StandardCacheManager cacheManager = new StandardCacheManager();
        cacheManager.setTemplateCacheMaxSize(500);
        cacheManager.setFragmentCacheMaxSize(1000);
        return cacheManager;
    }

}

Cette classe utilise deux dialects de Thymeleaf : ConditionalComments et Layout (non inclus nativement dans la version 2.1.4.RELEASE utilisée) et deux paramétrages de cache au niveau du resolver et du manager qui dépendent d'un @Profile Spring utilisé lors du lancement du jar exécutable final :

<dependency>
	<groupId>nz.net.ultraq.thymeleaf</groupId>
	<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>

<dependency>
	<groupId>org.thymeleaf.extras</groupId>
	<artifactId>thymeleaf-extras-conditionalcomments</artifactId>
</dependency>

les templates Thymeleaf étant lus dans un autre répertoire que celui par défaut (src/main/resources/templates/), il faut obligatoirement surcharger la properties suivante dans votre application.properties comme ci-dessous :

spring.thymeleaf.check-template-location=false

Avantages

  • La facilité avec laquelle Thymeleaf s'intègre dans un projet Spring : une simple classe @Configuration voir ci-dessus

  • Nous écrivons des fichiers .html, et les fichiers sont à la fois lisibles par l'intégrateur web et le développeur java par exemple :

Statique

<div class="row">
    <div class="columns small-24 medium-22 large-22 title-commande">
        <p class="highlighted-title">Votre commande</p>
    </div>
</div>

Dynamique

<div class="row">
    <div class="columns small-24 medium-22 large-22 title-commande">
        <p class="highlighted-title" th:text="#{page.cart.title}">Votre commande</p>
    </div>
</div>
  • La possibilité d'écrire des fonctions/macros, même si cela reste un peu verbeux à l'utilisation :

Définition

<!--/*
    Affichage d'un mot au singulier ou au pluriel selon la quantité donnée en paramètre :

    pluralize(5, "article", "articles") => 5 articles
    pluralize(1, "vêtement", "vêtements") => 1 vêtement
*/-->
<div th:fragment="pluralize(qty, word, words)">
    <th:block th:text="${qty} + ' ' + ${qty > 1 ? words : word}">10 articles</th:block>
</div>

Utilisation

<th:block th:include="common/functions/common_functions :: pluralize (${cart.totalQuantityProducts}, ${#strings.toUpperCase(#messages.msg('page.cart.article'))}, ${#strings.toUpperCase(#messages.msg('page.cart.articles'))})">3 ARTICLES</th:block>
  • L'utilisation des beans Spring (annotés @Component ou @Service par exemple) à l'intérieur même de nos fichiers html :

Html

<!--/*
    Transformation d'un prix en centimes en prix en euros :

    toPrice(15090) => 150,90 €
*/-->
<div th:fragment="toPrice(centimes)">
    <th:block th:if="${centimes}">
        <th:block th:text="${@priceUtil.formatPrice(centimes)}">100,90</th:block>&nbsp;&euro;
    /th:block>
</div>

Java

@Component
public class PriceUtil {

    /**
     * Formatting price for Thymeleaf
     */
    public String formatPrice(Integer price) {
        return formatCentimes(price, ",").orElse("");
    }

    /**
     * 15990 -> "159.90"
     */
    private Optional<String> formatCentimes(Integer price, String separator) {
        if (price == null) {
            return Optional.empty();
        }

        String strPrice = String.valueOf(price);
        String integer;
        String decimal;

        if (price.intValue() < 100) {
            integer = "0";
            if (price.intValue() < 10) {
                decimal = "0" + strPrice;
            } else {
                decimal = strPrice;
            }
        } else {
            integer = strPrice.substring(0, strPrice.length() - 2);
            decimal = strPrice.substring(strPrice.length() - 2);
        }

        return Optional.of(integer + separator + decimal);
    }
}
  • Une documentation bien écrite et donc pratique, c'est souvent là que se joue l'adoption ou non d'un nouveau framework : Tutorial: Using Thymeleaf

  • Un forum actif au cas où vous rencontrez des problèmes insurmontables : Thymeleaf - User Forum

Benchmark

Côté performance ce n'est pas la joie, voyez plutôt ce graphique qui montre que Thymeleaf est clairement en retard sur ses concurrents.
Personnellement ça me dérange un peu, surtout que la différence est nette, c'est pour cela que j'attends avec impatience la version 3.0.0-RELEASE du framework qui a repensé complètement son modèle en vue d'une amélioration des performances, actuellement en BETA02.

Java Template Engine Performance Comparison

Si vous souhaitez tester par vous même les performances de Thymeleaf, voici des liens qui pourront vous intéresser :