«1. Обзор

Spring Expression Language (SpEL) — это мощный язык выражений, поддерживающий запросы и манипулирование графом объектов во время выполнения. Его можно использовать с конфигурациями Spring на основе XML или аннотаций.

В языке доступно несколько операторов:

Type Operators
Arithmetic +, -, *, /, %, ^, div, mod
Relational <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge
Logical and, or, not, &&, ||, !
Conditional ?:
Regex matches

2. Операторы

В этих примерах мы будем использовать конфигурацию на основе аннотаций. Дополнительные сведения о конфигурации XML можно найти в последующих разделах этой статьи.

Выражения SpEL начинаются с символа # и заключаются в фигурные скобки: #{выражение}. На свойства можно ссылаться аналогичным образом, начиная с символа $ и заключая их в фигурные скобки: ${property.name}. Заполнители свойств не могут содержать выражения SpEL, но выражения могут содержать ссылки на свойства:

#{${someProperty} + 2}

В приведенном выше примере предположим, что someProperty имеет значение 2, поэтому результирующее выражение будет 2 + 2, которое будет оцениваться как 4.

2.1. Арифметические операторы

Поддерживаются все основные арифметические операторы.

@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;

Операции деления и деления по модулю имеют алфавитные псевдонимы, div для / и mod для %. Оператор + также может использоваться для объединения строк.

2.2. Реляционные и логические операторы

Также поддерживаются все основные реляционные и логические операции.

@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;

Все реляционные операторы также имеют алфавитные псевдонимы. Например, в конфигах на основе XML нельзя использовать операторы, содержащие угловые скобки (\u003c, \u003c=, \u003e, \u003e=). Вместо этого мы можем использовать lt (меньше чем), le (меньше или равно), gt (больше чем) или ge (больше или равно).

2.3. Логические операторы

SpEL поддерживает все основные логические операции:

@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;

Как и арифметические операторы и операторы отношения, все логические операторы также имеют алфавитные копии.

2.4. Условные операторы

Условные операторы используются для ввода различных значений в зависимости от некоторого условия:

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

Тернарный оператор используется для выполнения компактной условной логики if-then-else внутри выражения. В этом примере мы пытаемся проверить, правда это или нет.

Еще одно распространенное использование тернарного оператора — проверить, является ли какая-либо переменная нулевой, а затем вернуть значение переменной или значение по умолчанию:

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

Оператор Элвиса — это способ сокращения синтаксиса тернарного оператора для случая выше используется в языке Groovy. Он также доступен в SpEL. Код ниже эквивалентен коду выше:

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

2.5. Использование регулярных выражений в SpEL

Оператор совпадений можно использовать для проверки того, соответствует ли строка заданному регулярному выражению.

@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. Доступ к объектам списка и карты

С помощью SpEL мы можем получить доступ к содержимому любой карты или списка в контексте. Мы создадим новый bean-компонент workerHolder, который будет хранить информацию о некоторых работниках и их зарплатах в списке и на карте:

@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
}

Теперь мы можем получить доступ к значениям коллекций с помощью 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. Использовать в конфигурации Spring

3.1. Ссылка на бин

В этом примере мы рассмотрим, как использовать SpEL в конфигурации на основе XML. Выражения могут использоваться для ссылки на бины или поля/методы бинов. Например, предположим, что у нас есть следующие классы:

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
}

Теперь мы создаем контекст приложения, в котором выражения используются для ввода значений:

<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>

Взгляните на bean-компонент someCar. Поля engine и horsePower в someCar используют выражения, которые являются ссылками на bean-компонент engine и поле horsePower соответственно.

Чтобы сделать то же самое с конфигурациями на основе аннотаций, используйте аннотацию @Value(“#{expression}“).

3.2. Использование операторов в конфигурации

Каждый оператор из первого раздела этой статьи можно использовать в конфигурациях на основе XML и аннотаций. Однако помните, что в конфигурации на основе XML мы не можем использовать оператор угловых скобок «\u003c». Вместо этого мы должны использовать алфавитные псевдонимы, такие как lt (меньше чем) или le (меньше или равно). Для конфигураций на основе аннотаций таких ограничений нет.

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
    }

<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>

Теперь мы добавим bean-компонент spelOperators в контекст приложения:

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

«

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

«Извлекая этот bean-компонент из контекста, мы можем затем убедиться, что значения были введены правильно:

Здесь мы можем увидеть вывод метода toString bean-компонента spelOperators:

4. Программный разбор выражений ~~ ~ Иногда нам может понадобиться проанализировать выражения вне контекста конфигурации. К счастью, это возможно с помощью SpelExpressionParser. Мы можем использовать все операторы, которые видели в предыдущих примерах, но должны использовать их без фигурных скобок и символа решетки. То есть, если мы хотим использовать выражение с оператором + при использовании в конфигурации Spring, синтаксис будет #{1 + 1}; при использовании вне конфигурации используется простой синтаксис 1 + 1.

В следующих примерах мы будем использовать bean-компоненты Car и Engine, определенные в предыдущем разделе.

4.1. Использование ExpressionParser

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

Давайте рассмотрим простой пример:

ExpressionParser отвечает за анализ строк выражений. В этом примере синтаксический анализатор SpEL просто оценит строку Any String как выражение. Неудивительно, что результатом будет «Любая строка».

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

Как и при использовании SpEL в конфигурации, мы можем использовать его для вызова методов, доступа к свойствам или вызова конструкторов.

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

Кроме того, вместо того, чтобы напрямую работать с литералом, мы могли бы вызвать конструктор: представление строки:

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

Мы можем связать вызовы методов, как в обычном коде Java:

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

В этом случае результатом будет 9, потому что мы заменили пробелы пустой строкой. Если мы не хотим приводить результат выражения, мы можем использовать общий метод T getValue(Class\u003cT\u003e requiredResultType), в котором мы можем указать желаемый тип класса, который мы хотим вернуть. Обратите внимание, что EvaluationException будет сгенерировано, если возвращаемое значение не может быть приведено к желаемому ResultType:

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

Наиболее распространенное использование — предоставить строку выражения, которая оценивается для определенного экземпляра объекта:

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);

В этом случае , результат будет равен значению поля модели объекта автомобиля «Модель 3». Класс StandardEvaluationContext указывает, для какого объекта будет оцениваться выражение.

Его нельзя изменить после создания объекта контекста. Конструктор StandardEvaluationContext требует больших затрат, и при повторном использовании он создает кэшированное состояние, что позволяет быстрее выполнять последующие вычисления выражений. Из-за кэширования рекомендуется повторно использовать StandardEvaluationContext, если это возможно, если корневой объект не изменяется.

Однако, если корневой объект неоднократно изменяется, мы можем использовать механизм, показанный в примере ниже:

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

Здесь мы вызываем метод getValue с аргументом, представляющим объект, к которому мы хотим применить SpEL-выражение. Мы также можем использовать общий метод getValue, как и раньше:

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

4.2. Использование ExpressionParser для установки значения

Используя метод setValue для объекта Expression, возвращаемого при синтаксическом анализе выражения, мы можем устанавливать значения для объектов. SpEL позаботится о преобразовании типов. По умолчанию SpEL использует org.springframework.core.convert.ConversionService. Мы можем создать собственный преобразователь между типами. ConversionService поддерживает дженерики, поэтому его можно использовать с дженериками. Давайте посмотрим, как мы можем использовать это на практике:

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");

Результирующий автомобильный объект будет иметь модель «Другая модель», которая была изменена с «Модели 3».

4.3. Конфигурация парсера

В следующем примере мы будем использовать следующий класс:

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

    // Getter and setter
}

Можно настроить ExpressionParser, вызвав конструктор с объектом SpelParserConfiguration. Например, если мы попытаемся добавить объект car в массив cars класса CarPark без настройки парсера, то получим такую ​​ошибку:

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

«

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);

«Мы можем изменить поведение синтаксического анализатора, чтобы позволить ему автоматически создавать элементы, если указанный индекс равен нулю (autoGrowNullReferences, первый параметр конструктора), или автоматически увеличивать массив или список для размещения элементов, превышающих его первоначальный размер (autoGrowCollections , второй параметр).

Результирующий объект car будет равен объекту car, который был установлен в качестве первого элемента массива cars объекта carPark из предыдущего примера.

5. Заключение

SpEL — это мощный, хорошо поддерживаемый язык выражений, который можно использовать во всех продуктах портфолио Spring. Его можно использовать для настройки приложений Spring или для написания парсеров для выполнения более общих задач в любом приложении.