Le langage d’expression SpEL

Un langage d’expression est un langage de programmation simplifié qui permet (comme son nom l’indique) d’évaluer une expression simple pour produire un résultat. Il s’agit assez souvent de parcourir un graphe d’objets pour accéder à la valeur d’une propriété. En Java EE, il existe un langage d’expression (nommé simplement EL pour Expression Language) qui permet, notamment, d’accéder à des propriétés pour produire une page Web dynamiquement (avec JSP ou JSF).

Le Spring Framework introduit son propre langage d’expression (nommé SpEL pour Spring Expression Language). Il est très similaire à l’EL Java EE (tout en étant plus expressif). Pour la configuration d’un contexte d’application, ce langage permet l’évaluation d’expressions pour désigner le bean ou la valeur à injecter. Lorsqu’elle est écrite comme valeur de l’annotation @Value, une expression SpEL est délimitée par #{ } .

Nous verrons dans le chapitre Les applications Web avec Spring Web MVC que les expressions SpEL sont surtout utiles pour produire des pages Web dynamiquement à partir du moteur de rendu Thymeleaf.

Note

Pour une présentation complète du langage SpEL, reportez-vous à la documentation officielle.

Un exemple d’utilisation de SpEL

Imaginons que notre application déclare une classe RemoteServiceConfiguration qui possède des informations sur la connexion à un service.

Exemple de classe Configuration
package dev.gayerie;

import java.net.URL;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RemoteServiceConfiguration {

  @Value("${remote.service.url}")
  private URL url;

  @Value("${remote.service.connection.timeout : 1000}")
  private int connectionTimeout;

  public URL getUrl() {
    return url;
  }

  public int getConnectionTimeout() {
    return connectionTimeout;
  }
}

Comme nous l’avons vu au chapitre précédent, nous pouvons utiliser l’annotation @Value pour injecter des valeurs extraites d’un fichier de propriétés.

Nous déclarons également dans l’application une classe RemoteService qui est responsable de se connecter au service distant à partir des informations fournies par le bean de type RemoteServiceConfiguration.

Une première implémentation pourrait être :

Exemple de classe Service
package dev.gayerie;

import java.io.IOException;
import java.net.URLConnection;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RemoteService {

  @Autowired
  private RemoteServiceConfiguration remoteServiceConfiguration;

  @PostConstruct
  public void connect() throws IOException {
    URLConnection openConnection = remoteServiceConfiguration.getUrl().openConnection();
    openConnection.setConnectTimeout(remoteServiceConfiguration.getConnectionTimeout());

    // ...
  }

}

Même si cette implémentation fonctionne parfaitement, elle suppose que la classe RemoteService à une dépendance avec la classe RemoteServiceConfiguration. Nous pouvons préférer que Spring injecte le contenu des propriétés url et connectionTimeout dans des attributs de la classe RemoteService. Ainsi la dépendance directe sera supprimée. La classe RemoteService pourrait alors être implémentée comme ceci :

Exemple de classe Service avec l’utilisation de spEL
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package dev.gayerie;

import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class RemoteService {

  @Value("#{remoteServiceConfiguration.url}")
  private URL url;
  @Value("#{remoteServiceConfiguration.connectionTimeout}")
  private int connectionTimeout;

  @PostConstruct
  public void connect() throws IOException {
    URLConnection openConnection = url.openConnection();
    openConnection.setConnectTimeout(connectionTimeout);

    // ...
  }

}

À la ligne 13 et à la ligne 14, nous utilisons deux expressions SpEL pour injecter les propriétés du bean remoteServiceConfiguration. Notez d’abord que c’est le nom du bean qui est utilisé dans l’expression, en utilisant la convention par défaut de Spring qui nomme le bean suivant le nom de la classe en commençant par une lettre minuscule. Notez ensuite que l’expression commence par #{ pour ne pas la confondre avec la référence à une propriété d’un fichier de configuration ou une variable d’environnement.

Principe de SpEL

Avec une expression SpEL, on peut référencer n’importe quel bean dans une expression par son nom et on peut accéder à ses propriétés grâce à l’opérateur ..

On peut également utiliser les opérateurs arithmétiques et logiques dans une expression.

Quelques exemples d’expressions avec SpEL
produit.dateLimite

produit.quantite - 1

1024 * 1024 * 1024

! produit.epuise && produit.quantite > 0 && produit.quantite < 100

SpEL supporte la même écriture que Java mais, pour les opérateurs logiques, il est possible d’utiliser une forme textuelle :

Utilisation de la forme textuelle pour les opérateurs logiques
not produit.epuise and produit.quantite gt 0 and produit.quantite lt 100

Il est également possible d’appeler des méthodes sur des beans :

produit.nom.toUppercase()

ou des méthodes statiques en donnant le nom de la classe entre T( ) :

T(java.lang.Math).min(stock.prixPlancher, stock.prixAlerte)

Note

L’opérateur spécial T(...) permet d’indiquer dans une expression que l’on référence une classe et non pas un bean.

Il est possible de manipuler le contenu de tableaux, de listes ou de dictionnaires (Map) grâce à l’opérateur [].

Il existe un objet implicite nommé « systemProperties » qui correspond à un dictionnaire des propriétés systèmes au moment de l’exécution de la JVM. L’expression suivante :

systemProperties['user.name']

retourne la valeur de la propriété système user.name qui correspond au login de la session utilisateur qui exécute la JVM.

Il est également possible de créer dans une expression, un tableau, une liste ou un dictionnaire (Map) :

Expression pour la création d’une liste ou d’un tableau
{1,2,3,4}
Expression pour la création d’un dictionnaire
{prenom: 'David', nom: 'Gayerie'}

L’opérateur ternaire

SpEL supporte l’opérateur ternaire de la forme expression ? si vraie : si fausse.

Expression ternaire
connection.timeout < 0 ? 0 : connection.timeout

L’opérateur Elvis

Il existe une variante de l’opérateur ternaire, appelée l’opérateur Elvis. Elle permet d’écrire plus simplement l’expression suivante :

Expression ternaire pour contrôler la nullité
warning.message != null < warning.message ? "Warning"

Lorsque nous voulons simplement déclarer une valeur par défaut si l’expression est nulle, nous pouvons utiliser l’opérateur Elvis :

Expression avec l’opérateur Elvic
warning.message ?: "Warning"

L’opérateur de sûreté

L’opérateur de sûreté (safe navigation operator) ?. permet d’éviter une erreur de type NullPointerException si une propriété dans l’expression vaut null. Dans ce cas, le résultat de l’expression est simplement null :

Expression avec l’opérateur de sûreté
service?.connection?.url

La projection

Un usage plus avancé des expressions est la projection. Imaginons que nous disposons d’un bean annuaire qui contient une liste de personnes. Chaque personne dispose d’une adresse contenant le nom de la ville. Si nous souhaitons récupérer la liste de toutes les villes nous pouvons utiliser une projection :

Expression avec une projection
annuaire.personnes.![adresse.ville]