1. Обзор
Spring Expression Language (SpEL) — это мощный язык выражений, поддерживающий запросы и управление графом объектов во время выполнения. Мы можем использовать его с конфигурациями Spring на основе XML или аннотаций.
В языке доступно несколько операторов:
| **Тип** | **Операторы** |
| Арифметика | +, -, *, /, %, ^, деление, модуль |
| Относительный | <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge |
| Логический | и, или, нет, &&, ||, ! |
| Условный | ?: |
| регулярное выражение | Спички |
2. Операторы
Для этих примеров мы будем использовать конфигурацию на основе аннотаций. Дополнительные сведения о конфигурации XML см. в последующих разделах этой статьи.
Выражения SpEL начинаются с символа # и заключаются в фигурные скобки:
#{expression}
.
На свойства можно ссылаться аналогичным образом, начиная с символа $ и заключая в фигурные скобки:
${property.name}
.
Заполнители свойств не могут содержать выражения SpEL, но выражения могут содержать ссылки на свойства:
#{${someProperty} + 2}
В приведенном выше примере предположим, что someProperty
имеет значение 2, поэтому результирующее выражение будет 2 + 2, которое будет оцениваться как 4.
2.1. Арифметические операторы
SpEL поддерживает все основные арифметические операторы:
@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. Реляционные и логические операторы
SpEL также поддерживает все основные реляционные и логические операции:
@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 нельзя использовать операторы, содержащие угловые скобки ( <
, <=
, >
, >=
). Вместо этого мы можем использовать 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
Мы можем использовать оператор match, чтобы проверить, соответствует
ли строка заданному регулярному выражению:
@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
, который будет хранить информацию о некоторых работниках и их зарплатах в List
и Map
:
@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. Мы можем использовать выражения для ссылки на bean-компоненты или поля/методы bean-компонентов.
Например, предположим, что у нас есть следующие классы:
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.foreach.spring.spel.Engine">
<property name="capacity" value="3200"/>
<property name="horsePower" value="250"/>
<property name="numberOfCylinders" value="6"/>
</bean>
<bean id="someCar" class="com.foreach.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 мы не можем использовать оператор угловых скобок «<». Вместо этого мы должны использовать алфавитные псевдонимы, такие как 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-компонент spelOperators
в контекст приложения:
<bean id="spelOperators" class="com.foreach.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>
Извлекая этот компонент из контекста, мы можем затем убедиться, что значения были введены правильно:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
SpelOperators spelOperators = (SpelOperators) context.getBean("spelOperators");
Здесь мы можем увидеть вывод метода toString bean-компонента
spelOperators
:
[equal=true, notEqual=false, greaterThanOrEqual=true, and=true,
or=true, addString=Some model manufactured by Some make]
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
как выражение. Неудивительно, что результатом будет Any String
.
Как и при использовании SpEL в конфигурации, мы можем использовать его для вызова методов, доступа к свойствам или вызова конструкторов:
Expression expression = expressionParser.parseExpression("'Any string'.length()");
Integer result = (Integer) expression.getValue();
Кроме того, вместо того, чтобы напрямую работать с литералом, мы могли бы вызвать конструктор:
Expression expression = expressionParser.parseExpression("new String('Any string').length()");
Мы также можем получить доступ к свойству bytes класса
String
таким же образом, что приведет к представлению строки byte[]:
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<T> 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
Мы можем изменить поведение синтаксического анализатора, чтобы позволить ему автоматически создавать элементы, если указанный индекс равен нулю ( autoGrowNullReferences
, первый параметр конструктора), или автоматически увеличивать массив или список для размещения элементов, превышающих его первоначальный размер ( autoGrowCollections
, второй параметр):
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);
Результирующий объект car
будет равен объекту car
, который был установлен в качестве первого элемента массива cars объекта
carPark
из предыдущего примера.
5. Вывод
SpEL — это мощный, хорошо поддерживаемый язык выражений, который мы можем использовать во всех продуктах портфолио Spring. Мы можем использовать его для настройки приложений Spring или для написания парсеров для выполнения более общих задач в любом приложении.
Примеры кода в этой статье доступны в связанном репозитории GitHub .