Guide du langage Spring Expression

1. Vue d’ensemble

Le langage d’expression Spring (SpEL) est un langage d’expression puissant qui prend en charge la requête et la manipulation d’un graphe d’objet lors de l’exécution. Il peut être utilisé avec des configurations XML ou Spring basées sur des annotations.

Il y a plusieurs opérateurs disponibles dans la langue:

| ============================================== ======= | Type | Opérateurs | Arithmétique | , -, ** ,/,%, ^, div, mod | Relational | <,>, ==,! =, ⇐,> =, lt, gt, eq, ne, le, ge | Logical | et, ou , ne pas,

2. Les opérateurs

Pour ces exemples, nous utiliserons une configuration basée sur des annotations. Vous trouverez plus de détails sur la configuration XML dans les sections suivantes de cet article.

Les expressions Spel commencent par le symbole # et sont entourées d’accolades:

#{expression} . Les propriétés peuvent être référencées de la même manière, en commençant par le symbole $ et en les entourant d’accolades: $ \ {property.name}

Les espaces réservés de propriété ne peuvent pas contenir d’expressions SpEL, mais les expressions peuvent contenir des références de propriété:

#{${someProperty} + 2}

Dans l’exemple ci-dessus, supposons que someProperty ait la valeur 2, ainsi l’expression résultante serait 2 2, ce qui serait évalué à 4.

** 2.1. Opérateurs Arithmétiques

**

Tous les opérateurs arithmétiques de base sont pris en charge.

@Value("#{19 + 1}")//20
private double add;

@Value("#{'String1 ' + 'string2'}")//"String1 string2"
private String addString;

@Value("#{20 - 1}")//19
private double subtract;

@Value("#{10 **  2}")//20
private double multiply;

@Value("#{36/2}")//19
private double divide;

@Value("#{36 div 2}")//18, the same as for/operator
private double divideAlphabetic;

@Value("#{37 % 10}")//7
private double modulo;

@Value("#{37 mod 10}")//7, the same as for % operator
private double moduloAlphabetic;

@Value("#{2 ^ 9}")//512
private double powerOf;

@Value("#{(2 + 2) **  2 + 9}")//17
private double brackets;

Les opérations de division et modulo ont des alias alphabétiques, div pour / et mod pour % . L’opérateur peut également être utilisé pour concaténer des chaînes.

** 2.2. Opérateurs relationnels et logiques

**

Toutes les opérations relationnelles et logiques de base sont également prises en charge.

@Value("#{1 == 1}")//true
private boolean equal;

@Value("#{1 eq 1}")//true
private boolean equalAlphabetic;

@Value("#{1 != 1}")//false
private boolean notEqual;

@Value("#{1 ne 1}")//false
private boolean notEqualAlphabetic;

@Value("#{1 < 1}")//false
private boolean lessThan;

@Value("#{1 lt 1}")//false
private boolean lessThanAlphabetic;

@Value("#{1 <= 1}")//true
private boolean lessThanOrEqual;

@Value("#{1 le 1}")//true
private boolean lessThanOrEqualAlphabetic;

@Value("#{1 > 1}")//false
private boolean greaterThan;

@Value("#{1 gt 1}")//false
private boolean greaterThanAlphabetic;

@Value("#{1 >= 1}")//true
private boolean greaterThanOrEqual;

@Value("#{1 ge 1}")//true
private boolean greaterThanOrEqualAlphabetic;

Tous les opérateurs relationnels ont également des alias alphabétiques. Par exemple, dans les configurations basées sur XML, nous ne pouvons pas utiliser d’opérateurs contenant des crochets ( < , ⇐, > , > = ). Au lieu de cela, nous pouvons utiliser lt (inférieur à), le (inférieur ou égal), gt (supérieur à) ou ge (supérieur ou égal).

2.3. Opérateurs logiques

SpEL prend en charge toutes les opérations logiques de base:

@Value("#{250 > 200 && 200 < 4000}")//true
private boolean and;

@Value("#{250 > 200 and 200 < 4000}")//true
private boolean andAlphabetic;

@Value("#{400 > 300 || 150 < 100}")//true
private boolean or;

@Value("#{400 > 300 or 150 < 100}")//true
private boolean orAlphabetic;

@Value("#{!true}")//false
private boolean not;

@Value("#{not true}")//false
private boolean notAlphabetic;

Comme pour les opérateurs arithmétiques et relationnels, tous les opérateurs logiques ont également des clones alphabétiques.

** 2.4. Opérateurs conditionnels

**

Les opérateurs conditionnels sont utilisés pour injecter différentes valeurs en fonction de certaines conditions:

@Value("#{2 > 1 ? 'a' : 'b'}")//"a"
private String ternary;

L’opérateur ternaire est utilisé pour effectuer une logique conditionnelle compacte if-then-else à l’intérieur de l’expression. Dans cet exemple, nous essayons de vérifier s’il y avait true ou pas.

Une autre utilisation courante de l’opérateur ternaire est de vérifier si une variable est null , puis de renvoyer la valeur de la variable ou une valeur par défaut:

@Value("#{someBean.someProperty != null ? someBean.someProperty : 'default'}")
private String ternary;

L’opérateur Elvis est un moyen de raccourcir la syntaxe de l’opérateur ternaire pour le cas ci-dessus utilisé dans le langage Groovy. Il est également disponible dans SpEL. Le code ci-dessous est équivalent au code ci-dessus:

@Value("#{someBean.someProperty ?: 'default'}")//Will inject provided string if someProperty is null
private String elvis;

2.5. Utiliser Regex dans SpEL

L’opérateur matches peut être utilisé pour vérifier si une chaîne correspond ou non à une expression régulière donnée.

@Value("#{'100' matches '\\d+' }")//true
private boolean validNumericStringResult;

@Value("#{'100fghdjf' matches '\\d+' }")//false
private boolean invalidNumericStringResult;

@Value("#{'valid alphabetic string' matches '[a-zA-Z\\s]+' }")//true
private boolean validAlphabeticStringResult;

@Value("#{'invalid alphabetic string #$1' matches '[a-zA-Z\\s]+' }")//false
private boolean invalidAlphabeticStringResult;

@Value("#{someBean.someValue matches '\d+'}")//true if someValue contains only digits
private boolean validNumericValue;

** 2.6. Accéder aux objets List et Map

**

Avec l’aide de SpEL, nous pouvons accéder au contenu de n’importe quel Map ou List dans le contexte. Nous allons créer un nouveau bean workersHolder qui stockera des informations sur certains travailleurs et leurs salaires dans une liste et un tableau:

@Component("workersHolder")
public class WorkersHolder {
    private List<String> workers = new LinkedList<>();
    private Map<String, Integer> salaryByWorkers = new HashMap<>();

    public WorkersHolder() {
        workers.add("John");
        workers.add("Susie");
        workers.add("Alex");
        workers.add("George");

        salaryByWorkers.put("John", 35000);
        salaryByWorkers.put("Susie", 47000);
        salaryByWorkers.put("Alex", 12000);
        salaryByWorkers.put("George", 14000);
    }

   //Getters and setters
}

Nous pouvons maintenant accéder aux valeurs des collections en utilisant SpEL:

@Value("#{workersHolder.salaryByWorkers['John']}")//35000
private Integer johnSalary;

@Value("#{workersHolder.salaryByWorkers['George']}")//14000
private Integer georgeSalary;

@Value("#{workersHolder.salaryByWorkers['Susie']}")//47000
private Integer susieSalary;

@Value("#{workersHolder.workers[0]}")//John
private String firstWorker;

@Value("#{workersHolder.workers[3]}")//George
private String lastWorker;

@Value("#{workersHolder.workers.size()}")//4
private Integer numberOfWorkers;

** 3. Utilisation en configuration de printemps

**

** 3.1. Référencer un haricot

**

Dans cet exemple, nous verrons comment utiliser SpEL dans une configuration basée sur XML. Les expressions peuvent être utilisées pour référencer des haricots ou des champs/méthodes de haricots. Par exemple, supposons que nous ayons les classes suivantes:

public class Engine {
    private int capacity;
    private int horsePower;
    private int numberOfCylinders;

  //Getters and setters
}

public class Car {
    private String make;
    private int model;
    private Engine engine;
    private int horsePower;

  //Getters and setters
}

Nous créons maintenant un contexte d’application dans lequel des expressions sont utilisées pour injecter des valeurs:

<bean id="engine" class="com.baeldung.spring.spel.Engine">
   <property name="capacity" value="3200"/>
   <property name="horsePower" value="250"/>
   <property name="numberOfCylinders" value="6"/>
</bean>
<bean id="someCar" class="com.baeldung.spring.spel.Car">
   <property name="make" value="Some make"/>
   <property name="model" value="Some model"/>
   <property name="engine" value="#{engine}"/>
   <property name="horsePower" value="#{engine.horsePower}"/>
</bean>

Regardez le bean someCar . Les champs engine et horsePower de someCar utilisent des expressions qui sont des références de beans aux champs engine bean et horsePower , respectivement.

Pour faire la même chose avec les configurations basées sur des annotations, utilisez l’annotation @ Value (“# \ {expression}”) .

** 3.2. Utilisation d’opérateurs dans la configuration

**

Chaque opérateur de la première section de cet article peut être utilisé dans des configurations XML et basées sur des annotations. Cependant, rappelez-vous que dans la configuration basée sur XML, nous ne pouvons pas utiliser l’opérateur de crochet "<". À la place, nous devrions utiliser les alias alphabétiques, tels que lt (inférieur à) ou le (inférieur ou égal à). Pour les configurations basées sur des annotations, il n’y a pas de telles restrictions.

public class SpelOperators {
    private boolean equal;
    private boolean notEqual;
    private boolean greaterThanOrEqual;
    private boolean and;
    private boolean or;
    private String addString;

   //Getters and setters
    @Override
    public String toString() {
       //toString which include all fields
    }

Nous allons maintenant ajouter un bean spelOperators au contexte de l’application:

<bean id="spelOperators" class="com.baeldung.spring.spel.SpelOperators">
   <property name="equal" value="#{1 == 1}"/>
   <property name="notEqual" value="#{1 lt 1}"/>
   <property name="greaterThanOrEqual" value="#{someCar.engine.numberOfCylinders >= 6}"/>
   <property name="and" value="#{someCar.horsePower == 250 and someCar.engine.capacity lt 4000}"/>
   <property name="or" value="#{someCar.horsePower > 300 or someCar.engine.capacity > 3000}"/>
   <property name="addString" value="#{someCar.model + ' manufactured by ' + someCar.make}"/>
</bean>

En récupérant ce haricot du contexte, nous pouvons alors vérifier que les valeurs ont été injectées correctement:

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
SpelOperators spelOperators = (SpelOperators) context.getBean("spelOperators");

Nous pouvons voir ici le résultat de la méthode toString du bean spelOperators :

----[equal=true, notEqual=false, greaterThanOrEqual=true, and=true,
or=true, addString=Some model manufactured by Some make]----

** 4. Analyser les expressions par programme

**

Parfois, nous pouvons vouloir analyser des expressions en dehors du contexte de configuration. Heureusement, cela est possible en utilisant SpelExpressionParser . Nous pouvons utiliser tous les opérateurs que nous avons vus dans les exemples précédents, mais nous devrions les utiliser sans accolades ni symbole de hachage. En d’autres termes, si nous souhaitons utiliser une expression avec l’opérateur lorsqu’elle est utilisée dans la configuration Spring, la syntaxe est # \ {1 1}; en dehors de la configuration, la syntaxe est simplement 1 1 .

Dans les exemples suivants, nous allons utiliser les beans Car et Engine définis dans la section précédente.

** 4.1. Utiliser ExpressionParser

**

Voyons un exemple simple:

ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression("'Any string'");
String result = (String) expression.getValue();

ExpressionParser est responsable de l’analyse des chaînes d’expression. Dans cet exemple, l’analyseur SpEL évaluera simplement la chaîne ’Any String ’ comme une expression. Sans surprise, le résultat sera ʻAny String ’ .

Comme avec SpEL dans la configuration, nous pouvons l’utiliser pour appeler des méthodes, des propriétés d’accès ou des constructeurs.

Expression expression = expressionParser.parseExpression("'Any string'.length()");
Integer result = (Integer) expression.getValue();

De plus, au lieu d’opérer directement sur le littéral, nous pourrions appeler le constructeur:

Expression expression = expressionParser.parseExpression("new String('Any string').length()");

Nous pouvons également accéder à la propriété bytes de String class, de la même manière, ce qui donne la représentation octet[]de la chaîne:

Expression expression = expressionParser.parseExpression("'Any string'.bytes");
byte[]result = (byte[]) expression.getValue();

Nous pouvons chaîner des appels de méthode, comme dans le code Java normal:

Expression expression = expressionParser.parseExpression("'Any string'.replace(\" \", \"\").length()");
Integer result = (Integer) expression.getValue();

Dans ce cas, le résultat sera 9, car nous avons remplacé les espaces blancs par la chaîne vide. Si nous ne souhaitons pas transtyper le résultat de l’expression, nous pouvons utiliser la méthode générique T getValue (Classe <T> désiréResultType) , dans laquelle nous pouvons fournir le type de classe que nous voulons renvoyer. Notez que EvaluationException sera levée si la valeur renvoyée ne peut pas être convertie en desiredResultType :

Integer result = expression.getValue(Integer.class);

L’utilisation la plus courante consiste à fournir une chaîne d’expression évaluée par rapport à une instance d’objet spécifique:

Car car = new Car();
car.setMake("Good manufacturer");
car.setModel("Model 3");
car.setYearOfProduction(2014);

ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression("model");

EvaluationContext context = new StandardEvaluationContext(car);
String result = (String) expression.getValue(context);

Dans ce cas, le résultat sera égal à la valeur du champ model de l’objet car , « Model 3 ». La classe StandardEvaluationContext spécifie l’objet avec lequel l’expression sera évaluée.

Il ne peut pas être modifié après la création de l’objet de contexte.

La construction de StandardEvaluationContext est coûteuse et, lors d’utilisations répétées, elle crée un état mis en cache qui permet aux évaluations d’expressions ultérieures d’être exécutées plus rapidement. En raison de la mise en cache, il est recommandé de réutiliser StandardEvaluationContext lorsque cela est possible si l’objet racine ne change pas.

Toutefois, si l’objet racine est modifié à plusieurs reprises, nous pouvons utiliser le mécanisme présenté dans l’exemple ci-dessous:

Expression expression = expressionParser.parseExpression("model");
String result = (String) expression.getValue(car);

Ici, nous appelons la méthode getValue avec un argument qui représente l’objet auquel nous voulons appliquer une expression SpEL. Nous pouvons également utiliser la méthode générique getValue , comme auparavant:

Expression expression = expressionParser.parseExpression("yearOfProduction > 2005");
boolean result = expression.getValue(car, Boolean.class);

** 4.2. Utiliser ExpressionParser pour définir une valeur

**

En utilisant la méthode setValue sur l’objet Expression renvoyé en analysant une expression, nous pouvons définir des valeurs pour des objets. SpEL se chargera de la conversion de type. Par défaut, SpEL utilise org.springframework.core.convert.ConversionService . Nous pouvons créer notre propre convertisseur personnalisé entre les types. ConversionService est générique, il peut donc être utilisé avec des génériques. Voyons comment l’utiliser dans la pratique:

Car car = new Car();
car.setMake("Good manufacturer");
car.setModel("Model 3");
car.setYearOfProduction(2014);

CarPark carPark = new CarPark();
carPark.getCars().add(car);

StandardEvaluationContext context = new StandardEvaluationContext(carPark);

ExpressionParser expressionParser = new SpelExpressionParser();
expressionParser.parseExpression("cars[0].model").setValue(context, "Other model");

L’objet voiture résultant aura model « Other model », remplacé par « Model 3 ».

** 4.3. Configuration de l’analyseur

**

Dans l’exemple suivant, nous allons utiliser la classe suivante:

public class CarPark {
    private List<Car> cars = new ArrayList<>();

   //Getter and setter
}

Il est possible de configurer ExpressionParser en appelant le constructeur avec un objet SpelParserConfiguration _. Par exemple, si nous essayons d’ajouter car object dans le tableau cars de CarPark_ sans configurer l’analyseur, nous obtiendrons une erreur comme celle-ci:

EL1025E:(pos 4): The collection has '0' elements, index '0' is invalid

Nous pouvons modifier le comportement de l’analyseur pour lui permettre de créer automatiquement des éléments si l’index spécifié est null ( autoGrowNullReferences, le premier paramètre du constructeur), ou de développer automatiquement un tableau ou une liste afin de prendre en compte des éléments dépassant sa taille initiale ( autoGrowCollections , le deuxième paramètre).

SpelParserConfiguration config = new SpelParserConfiguration(true, true);
StandardEvaluationContext context = new StandardEvaluationContext(carPark);

ExpressionParser expressionParser = new SpelExpressionParser(config);
expressionParser.parseExpression("cars[0]").setValue(context, car);

Car result = carPark.getCars().get(0);

L’objet car résultant sera égal à l’objet car défini comme premier élément du tableau cars de l’objet carPark de l’exemple précédent.

5. Conclusion

SpEL est un langage d’expression puissant et bien supporté qui peut être utilisé dans tous les produits du portefeuille Spring. Il peut être utilisé pour configurer des applications Spring ou pour écrire des analyseurs pour effectuer des tâches plus générales dans n’importe quelle application.

Les exemples de code dans cet article sont disponibles dans le référentiel linked