Java – Spring Boot – Introduzione all’utilizzo dei template engine

Spring Boot è al giorno d’oggi uno dei framework più noti ed utilizzati per realizzare vari tipi di applicazioni Java. Con Spring Boot infatti si possono creare web application classiche (con interfaccia web tradizionale fatta in HTML), microservizi, web service REST/SOAP e altro. Se si parla di web application classiche, quelle in cui è l’applicazione che espone direttamente le pagine web, Spring Boot permette di usare dei template engine.

Questo articolo fornisce una introduzione generale ai concetti dei template engine e una panoramica su quelli che sono supportati ufficialmente in Spring Boot.

Motivazioni dei template engine in Spring Boot

Prima di passare alle sezioni un po’ più tecniche, è necessario dare una breve spiegazione delle motivazioni che portano all’utilizzo dei template engine.

Quando si facevano (e si fanno ancora oggi in certi contesti) delle applicazioni web Java con il framework Spring (intendo proprio Spring Framework, quello base, “puro”, non Boot), lo sviluppo tipicamente avviene nel seguente modo: si crea una web application secondo i canoni della piattaforma Java Enterprise (JavaEE) e si arriva a creare il pacchetto finale come file WAR (.war). Successivamente, viene fatto il deploy di questo file .war all’interno di un servlet container (es. Tomcat, Jetty, ecc...) oppure di un application server JavaEE completo (es. GlassFish, JBoss, ecc...).

In uno scenario come questo, è assolutamente lecito ed appropriato utilizzare le Java Server Pages (JSP) per generare dinamicamente le pagine web, in quanto le JSP sono una tecnologia standard che fa parte della piattaforma Java Enterprise.

Spring Boot però è qualcosa di più particolare, perché permette di creare delle applicazioni stand-alone, cioè che partono con il classico metodo main(String[]). Quando si usa Spring Boot in questa modalità, viene usato un web server embedded che è “incorporato” all’interno della applicazione. Per default viene configurato normalmente un Apache Tomcat embedded ma è possibile optare per un altro server (la scelta è tra Jetty oppure Undertow).
Quando si usa un web server embedded in Spring Boot, ci sono purtroppo alcune limitazioni ben note, che sono state descritte brevemente nella documentazione ufficiale di Spring Boot nella sezione 1.3.5. JSP Limitations.

In sostanza: quando si creano applicazioni stand-alone con Spring Boot, è sconsigliato l’utilizzo delle pagine JSP ed è invece consigliato l’utilizzo di un template engine.

Tra l’altro, i template engine possono risultare molto più utili, pratici ed efficienti. Bisogna infatti ricordare che le pagine JSP vengono innanzitutto trasformate nel codice sorgente di una Servlet, che viene poi compilata e infine messa a disposizione della JVM. Tutto questo è abbastanza macchinoso, oltre al fatto di richiedere la presenza di un compilatore Java. I template engine, al contrario, sono generalmente molto più “leggeri” e inoltre possono anche offrire delle funzionalità che le pagine JSP, per la loro natura e storia, non sono in grado di offrire.

Cosa sono i template engine

Un template engine è un “motore” che si occupa di generare dinamicamente dei documenti partendo da alcuni input forniti dalla applicazione. Ogni volta che un template engine deve generare un documento, riceve in input principalmente due informazioni: un template e un modello ad oggetti.

Il template è un file che è tipicamente incorporato all’interno della applicazione. Questo documento potrebbe essere ad esempio una pagina HTML che però, attenzione, non è il documento finale, cioè con dei dati reali. Il template invece è solamente lo “scheletro” generale del documento, poiché al suo interno ci possono essere dei “segnaposto” che servono ad indicare che in un certo punto si dovrà iniettare dinamicamente del contenuto (del testo, solitamente).

Nel template ci possono anche essere dei costrutti/direttive che permettono di modificare dinamicamente la struttura del documento, al fine di includere, escludere oppure ripetere più volte certe parti, ad esempio per generare dinamicamente le celle di una tabella o gli elementi degli elenchi puntati/numerati o altro.

Il modello ad oggetti invece contiene i dati reali, veri e propri, che possono arrivare sotto forma di bean Java (oggetti che rispettano al livello basilare le specifiche JavaBeans, cioè con i metodi getQualcosa/setQualcosa) oppure sotto forma di “mappe” (nel senso di java.util.Map) che contengono associazioni tra chiavi, di norma delle stringhe, e valori che sono oggetti di un qualunque tipo. Eventualmente è anche possibile gestire un mix di queste due cose (es. una mappa i cui valori sono dei bean Java).

Il template engine, ricevendo queste due informazioni, produce in output un documento combinando insieme il template con i dati reali. Il template in pratica può essere visto come una sorta di “modello” al cui interno ci sono i vari costrutti che indicano dove iniettare i dati e quali parti del documento devono essere incluse, escluse o ripetute.

Per avere una buona idea di tutto questo, si può vedere il seguente diagramma che è stato fatto basandosi in particolare sui concetti di Thymeleaf (uno dei template engine più conosciuti).

Diagramma Template Engine

Template engine utilizzabili con Spring Boot

Spring Boot offre un supporto “ufficiale” per 4 template engine:

Thymeleaf è uno dei template engine più noti ed utilizzati. A differenza di altri template engine, Thymeleaf è orientato esclusivamente alla generazione di documenti HTML e permette di applicare il principio di natural templating (un concetto che descriverò meglio in seguito).

FreeMarker è un altro template engine molto conosciuto e utilizzato. Diversamente da Thymeleaf, FreeMarker è in grado di trattare la generazione di vari tipi di documenti, tra cui HTML, XML e anche, volendo, testo “puro”. Per via della sua sintassi, che è un po’ più “invasiva”, non è in grado di applicare il principio di natural templating.

Mustache è un template engine davvero minimale e abbastanza particolare, che viene definito con il termine “logic-less” per il fatto che non possiede dei costrutti per il controllo esplicito del flusso (in pratica i concetti di if, else o for) ma fornisce solo dei “marcatori” che servono a delimitare delle sezioni che possono essere incluse, escluse o ripetute. Anche Mustache, per la sua sintassi, non applica il principio di natural templating.

Groovy templates è una ulteriore possibilità come sistema di templating, però molto molto più particolare rispetto agli altri. Groovy infatti è un linguaggio di programmazione “dinamico” per la JVM e nel contesto dei template engine si utilizza come DSL (Domain Specific Language). Quando si usa Groovy come template engine, in pratica si scrive un documento che “ricorda” vagamente la struttura di un certo linguaggio (es. HTML) ma in realtà quello che si sta scrivendo è effettivamente del codice Groovy. Per via di questa caratteristica, credo che i template Groovy siano in assoluto tra i meno utilizzati.

Con Spring Boot si possono anche eventualmente usare altri template engine (come il Pebble Templates) ma bisogna ricorrere a pacchetti/strumenti di terze parti, che non sono supportati ufficialmente da Pivotal (l’azienda dietro Spring).

Cosa serve per usare un template engine in Spring Boot

In questa sezione finale dell’articolo verranno descritti in generale i concetti e i passi necessari per utilizzare un template engine in Spring Boot, senza però entrare nei dettagli di ciascuno dei template engine elencati prima.

Quando si vuole utilizzare un template engine in Spring Boot, ci sono principalmente 3 aspetti da considerare:

  • come integrare il template engine in Spring Boot
  • come/dove scrivere i file dei template
  • cosa usare a livello dei Controller per rimandare al template passando anche i dati necessari

Come integrare il template engine in Spring Boot

In Spring Boot la integrazione di un template engine è una operazione davvero molto semplice, perlomeno per quelli che sono supportati “ufficialmente” da Spring Boot. Per altri template engine bisogna chiaramente documentarsi volta per volta sulla rispettiva documentazione, poiché potrebbero esserci altre questioni da valutare.

La integrazione di un template engine di norma passa attraverso l’utilizzo di un apposito starter. Il concetto di “starter” è tra quelli fondamentali che sono alla base della filosofia di Spring Boot. Uno starter è semplicemente una dipendenza che si inserisce nel pom.xml (per progetti Maven) o nel build.gradle (per progetti Gradle) al fine di “tirare dentro” nel progetto tutto quello che serve per un certo ambito/contesto.

Se ad esempio voglio usare MongoDB (un DB non relazionale) in Spring Boot, non devo preoccuparmi di andare a cercare e inserire “a mano” tutti gli artifact necessari, né devo valutare quali siano le versioni appropriate o meno di questi artifact. Invece è sufficiente inserire nel progetto lo starter chiamato spring-boot-starter-data-mongodb e questo porta già dentro nel progetto tutto ciò che è, ragionevolmente, il minimo/giusto indispensabile per poter iniziare ad usare MongoDB. E oltretutto che è già stato verificato e testato per quella specifica versione di Spring Boot in uso.

L’approccio con gli starter permette quindi di partire in maniera davvero veloce, pratica e valida con una certa tecnologia in Spring Boot.

Per i 4 template engine elencati in precedenza (Thymeleaf, FreeMarker, Mustache e Groovy), Spring Boot fornisce già degli starter “ufficiali” chiamati in generale spring-boot-starter-XXX dove XXX è il nome di un template engine (es. thymeleaf, freemarker). Rimando alla documentazione ufficiale per i dettagli.

Dal momento che i template engine in questo contesto lavorano al livello dello strato web MVC (Model-View-Controller), è obbligatorio utilizzare anche un altro starter, quello apposito per la parte web che si chiama spring-boot-starter-web.

Come/dove scrivere i file dei template

Il secondo aspetto da vedere riguarda dove e come devono essere scritti i file dei template. Tutti gli starter dei template engine supportati “ufficialmente” forniscono già una serie di auto-configurazioni e convenzioni predefinite che sono ragionevolmente valide per iniziare. Molti di questi preconcetti comunque si possono eventualmente modificare attraverso una serie di property di configurazione in Spring Boot.

Per default i template vengono cercati in un percorso /templates che deve trovarsi alla radice “in classpath”. Significa che i file dei template devono essere materialmente inseriti nel progetto Maven/Gradle sotto la seguente directory:

<nomeprogetto>/src/main/resources/templates

Il nome “base” del file del template è assolutamente a libera scelta, mentre invece l’estensione deve essere quella determinata dalla auto-configurazione dello starter. Per Thymeleaf l’estensione predefinita è .html mentre ad esempio per FreeMarker è .ftlh. Anche questa convenzione sulla estensione può essere facilmente cambiata con una apposita property di configurazione ma il consiglio è di non farlo a meno di avere motivazioni davvero valide.

La ragione per cui esistono questi preconcetti è per fare in modo che il Controller possa fornire solo il nome “logico” del template (es. "home") e ci pensa poi l’infrastruttura di Spring Boot a comporre il percorso da cercare “in classpath”, come ad esempio:

"home"
(nome logico)
――›/templates/home.estensione
(path cercato “in classpath”)

oppure es.:

"about/info"
(nome logico)
――›/templates/about/info.estensione
(path cercato “in classpath”)

Cosa usare a livello dei Controller

L’ultimo aspetto (forse il più importante) da vedere riguarda cosa scrivere in un Controller per poter rimandare correttamente ad un template passando anche dei dati.

Innanzitutto è bene chiarire che tutto questo riguarda solo ed esclusivamente lo strato dei Controller. Ciò che c’è eventualmente “sotto” il Controller (es. strati di Service, Dao/Repository, ecc...) è assolutamente ininfluente ai fini dell’utilizzo di un template engine per la parte web.

Come è già stato detto in precedenza, il Controller deve fornire in sostanza solo due informazioni: il template da utilizzare e un modello ad oggetti per i dati. Il template si indica semplicemente con il nome “logico” del template, che è una banale stringa tipo ad esempio "home". Per il modello ad oggetti invece la questione è un po’ più articolata in quanto esistono ad oggi vari modi per farlo, descritti di seguito.

Nella storia di Spring Framework (sto di nuovo parlando di quello base, “puro”, non Boot) sono stati ideati vari modi, nel corso del tempo, per poter esporre un modello ad oggetti da un Controller. Esistono quindi dei modi che sono più “vecchi” rispetto ad altri che invece sono più recenti.

Modo 1) La classe ModelAndView

La classe org.springframework.web.servlet.ModelAndView esiste praticamente da sempre in Spring, risale infatti alla prima versione 1.0 del framework. Come dice il nome ModelAndView, essa serve per incapsulare il modello ad oggetti e la “vista” a cui rimandare.

Per usare questa classe è sufficiente creare un oggetto ModelAndView, specificare la vista tipicamente tramite costruttore, aggiungere attributi e restituire questo oggetto dal metodo del controller. All’interno del ModelAndView c’è un modello ad oggetti basato su una banale map che associa chiavi di tipo String a valori di tipo Object. La classe fornisce anche dei metodi per aggiungere dati al modello:

  • public ModelAndView addAllObjects(Map<String, ?> modelMap)
  • public ModelAndView addObject(Object attributeValue)
  • public ModelAndView addObject(String attributeName, Object attributeValue)

Da notare che questi metodi restituiscono lo stesso medesimo oggetto ModelAndView, quindi è possibile sfruttare la tecnica del method-chaining.

Esempio:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HomeController {
    @GetMapping("/home")
    public ModelAndView getHomePage() {
        return new ModelAndView("home")   // nome logico del template
                .addObject("nome", "Andrea")
                .addObject("annoNascita", 1973);
    }
}

Modo 2) La interfaccia Model

La interfaccia org.springframework.ui.Model non esiste da sempre in Spring Framework, è stata introdotta infatti solo nella versione 2.5.1 di Spring. Model rappresenta solo il modello ad oggetti, non ha alcuna indicazione o riferimento ad una “vista” (o template, in questo caso).

Per questo motivo Model non può essere restituito dal controller, quindi il suo utilizzo è leggermente differente rispetto a ModelAndView. La soluzione indicata da Spring è di avere il Model come parametro del metodo e restituire un String con il nome logico della vista/template. Quando Spring chiamerà il metodo del controller, “sa” di dover passare una sua implementazione concreta di Model.

Anche Model fornisce una serie di metodi per aggiungere attributi restituendo lo stesso medesimo oggetto Model in modo da poter sfruttare il method-chaining:

  • Model addAllAttributes(Collection<?> attributeValues)
  • Model addAllAttributes(Map<String, ?> attributes)
  • Model addAttribute(Object attributeValue)
  • Model addAttribute(String attributeName, Object attributeValue)
  • Model mergeAttributes(Map<String, ?> attributes)

Esempio:

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
    @GetMapping("/home")
    public String getHomePage(Model model) {
        model.addAttribute("nome", "Andrea");
        model.addAttribute("annoNascita", 1973);
        return "home";   // nome logico del template
    }
}

Modo 3) La classe ModelMap

La classe org.springframework.ui.ModelMap è una ulteriore possibilità per popolare il model. ModelMap esiste in Spring a partire dalla versione 2.0 ed è stata realizzata come semplice estensione diretta di java.util.LinkedHashMap<String, Object>. Oltre ai metodi ereditati da LinkedHashMap (che sono comunque usabili), ne introduce altri che sono praticamente similari a quelli di Model per aggiungere attributi in modo più pratico, potendo anche sfruttare la tecnica del method-chaining:

  • ModelMap addAllAttributes(Collection<?> attributeValues)
  • ModelMap addAllAttributes(Map<String, ?> attributes)
  • ModelMap addAttribute(Object attributeValue)
  • ModelMap addAttribute(String attributeName, Object attributeValue)
  • ModelMap mergeAttributes(Map<String, ?> attributes)

ModelMap va usato come si fa con il Model, ovvero avendo un ModelMap come parametro del metodo nel Controller. Da notare che quando Spring invoca il metodo, non passa un oggetto esattamente di classe ModelMap ma una sua sottoclasse molto più specializzata (di norma un BindingAwareModelMap).

Esempio:

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
    @GetMapping("/home")
    public String getHomePage(ModelMap modelMap) {
        modelMap.addAttribute("nome", "Andrea")
                .addAttribute("annoNascita", 1973);
        return "home";   // nome logico del template
    }
}

Modo 4) java.util.Map

Una quarta possibilità per popolare il model, molto più basilare, è l’utilizzo di una semplice mappa java.util.Map<String, Object> come parametro del metodo nel Controller. Quando Spring invoca il metodo, passerà una sua implementazione concreta di Map (che di norma è sempre un BindingAwareModelMap).

La interfaccia Map è del framework standard di Java e purtroppo non offre metodi che restituiscono lo stesso medesimo oggetto su cui sono invocati, pertanto la tecnica del method chaining non è applicabile.

Esempio:

import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
    @GetMapping("/home")
    public String getHomePage(Map<String, Object> map) {
        map.put("nome", "Andrea");
        map.put("annoNascita", 1973);
        return "home";   // nome logico del template
    }
}

Conclusioni

Questo articolo è servito per fornire solo una visione generale sull’utilizzo dei template engine in Spring Boot. Ciascun singolo template engine meriterebbe infatti una descrizione più accurata con uno o più articoli di approfondimento.

A questo punto però qualcuno potrebbe già domandarsi quale può essere il template engine da considerare maggiormente scartando gli altri. La scelta chiaramente dipende molto dal contesto di utilizzo e dalle competenze di ciascun sviluppatore. Si possono comunque dare alcune indicazioni riguardo i 4 template engine elencati prima.

Thymeleaf è sicuramente da considerare se si intende generare solo documenti HTML (in particolare per la parte web della applicazione). È un template engine “moderno”, portato avanti già da svariati anni ed è tuttora in sviluppo attivo (la versione più recente nel momento in cui scrivo è del Dicembre 2021). Si può usare in molti contesti differenti: applicazioni JavaSE, applicazioni JavaEE con le Servlet, Spring Framework e Spring Boot.

Fornisce molte funzionalità interessanti, tra cui la possibilità di applicare il principio di natural templating. Non solo ha tutti i costrutti per il controllo del flusso ma fornisce anche delle expression specifiche che permettono di emettere in output gli url “corretti” per i link e anche i messaggi “localizzati” basandosi su tutta la infrastruttura di internazionalizzazione di Spring Boot.

FreeMarker è un altro template engine da considerare se si vuole generare pagine HTML. FreeMarker però è più generico, può generare anche altri formati (es. XML o testo puro). La sintassi dei suoi costrutti è più “invasiva” rispetto a Thymeleaf ma molto più “potente”. Il risvolto negativo di tutto questo è che la curva di apprendimento è sicuramente più alta rispetto ad altri template engine.

FreeMarker si usa sovente anche per altre cose, ad esempio per generare dinamicamente il “corpo” delle email da inviare in formato testo “puro” e/o HTML.

Mustache può essere una soluzione valida se non si devono fare cose particolarmente sofisticate e comunque se ci sono già le conoscenze di Mustache magari da altri contesti (infatti si può usare con tanti altri linguaggi di programmazione).

I template Groovy sono quelli più particolari, perché si utilizza il linguaggio Groovy come Domain Specific Language. Quello che si scrive nei template è sostanzialmente codice Groovy che “sembra” qualcos’altro (es. HTML). L’utilizzo dei template Groovy può aver senso solo se nel team/progetto c’è già una solida conoscenza di Groovy come linguaggio (magari già usato in quel progetto o per altri motivi). Altrimenti il consiglio che potrei dare è di lasciar perdere i template Groovy.