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.
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 :
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 :
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.
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 :
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) :
{1,2,3,4}
{prenom: 'David', nom: 'Gayerie'}
L’opérateur ternaire¶
SpEL supporte l’opérateur ternaire de la forme expression ? si vraie : si fausse
.
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 :
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 :
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
:
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 :
annuaire.personnes.![adresse.ville]