1. Обзор
Язык выражений Spring (SpEL) – это мощный язык выражений, который поддерживает запросы и управление графом объектов во время выполнения. Его можно использовать с конфигурациями пружин на основе XML или аннотаций.
На этом языке доступно несколько операторов:
| Арифметика | +, -, *, /, %, ^, div, mod |
| Реляционные | <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge |
| Логический | и, или, не, &&, ||, ! |
| Условный | ?: |
| Регулярное выражение | спички |
2. Операторы
Для этих примеров мы будем использовать конфигурацию на основе аннотаций. Более подробную информацию о конфигурации XML можно найти в последующих разделах этой статьи.
Выражения SpEL начинаются с символа # и заключаются в фигурные скобки: #{expression} . На свойства можно ссылаться аналогичным образом, начиная с символа $ и заключая их в фигурные скобки: ${property.name} . Заполнители свойств не могут содержать выражения SpEL, но выражения могут содержать ссылки на свойства:
#{${someProperty} + 2}В приведенном выше примере предположим, что какое-то свойство имеет значение 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 мы не можем использовать операторы, содержащие угловые скобки ( < , <=, > , >= ). Вместо этого мы можем использовать 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 внутри выражения. В этом примере мы пытаемся проверить, был ли true или нет.
Еще одним распространенным использованием тернарного оператора является проверка, является ли какая-либо переменная null , а затем возвращает значение переменной или значение по умолчанию:
@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
Оператор matches можно использовать для проверки соответствия строки заданному регулярному выражению.
@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 мы можем получить доступ к содержимому любой Карты или Списка в контексте. Мы создадим новый боб держатель работников , который будет хранить информацию о некоторых работниках и их зарплатах в Списке и Карте :
@Component("workersHolder")
public class WorkersHolder {
private List workers = new LinkedList<>();
private Map 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
} Теперь мы можем получить доступ к значениям коллекций с помощью заклинания:
@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. Используйте в весенней конфигурации
3.1. Ссылка на боб
В этом примере мы рассмотрим, как использовать заклинание в конфигурации на основе 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
}Теперь мы создаем контекст приложения, в котором выражения используются для ввода значений:
Взгляните на ту же машину бин. Поля двигатель и Лошадиная сила некоторых автомобилей используют выражения, которые являются ссылками на поле двигатель боб и Лошадиная сила соответственно.
Чтобы сделать то же самое с конфигурациями на основе аннотаций, используйте @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
}Теперь мы добавим Операторы spel в контекст приложения:
Извлекая этот компонент из контекста, мы можем затем проверить, что значения были введены правильно:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
SpelOperators spelOperators = (SpelOperators) context.getBean("spelOperators");
Здесь мы можем увидеть вывод метода toString операторов spel bean:
[equal=true, notEqual=false, greaterThanOrEqual=true, and=true, or=true, addString=Some model manufactured by Some make]
4. Программный Анализ Выражений
Иногда нам может потребоваться проанализировать выражения вне контекста конфигурации. К счастью, это возможно, используя SpelExpressionParser . Мы можем использовать все операторы, которые мы видели в предыдущих примерах, но должны использовать их без фигурных скобок и хэш-символа. То есть, если мы хотим использовать выражение с оператором + при использовании в конфигурации Spring, синтаксис #{1 + 1}; при использовании вне конфигурации синтаксис просто 1 + 1 .
В следующих примерах мы будем использовать компоненты Car и Engine , определенные в предыдущем разделе.
4.1. Использование анализатора Выражений
Давайте рассмотрим простой пример:
ExpressionParser expressionParser = new SpelExpressionParser();
Expression expression = expressionParser.parseExpression("'Any string'");
String result = (String) expression.getValue();
Синтаксический анализатор выражений отвечает за синтаксический анализ строк выражений. В этом примере синтаксический анализатор SpEL просто вычислит строку ‘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 desiredResultType) , в котором мы можем указать желаемый тип класса, который мы хотим вернуть. Обратите внимание, что EvaluationException будет выдано, если возвращаемое значение не может быть приведено к желаемому типу результата :
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);В этом случае результат будет равен значению поля model объекта car , ” Model 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. Использование анализатора выражений для установки значения
Используя метод setValue для объекта Expression , возвращаемого при разборе выражения, мы можем задавать значения для объектов. Заклинание позаботится о преобразовании типов. По умолчанию SpEL использует org.springframework.core.convert.Конверсионный сервис . Мы можем создать ваш собственный пользовательский конвертер между типами. Служба преобразования осведомлена о дженериках, поэтому ее можно использовать с дженериками. Давайте посмотрим, как мы можем использовать его на практике:
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 cars = new ArrayList<>();
// Getter and setter
} Можно настроить ExpressionParser , вызвав конструктор с SpelParserConfiguration object . Например, если мы попытаемся добавить car объект в массив cars класса Car Park без настройки синтаксического анализатора, мы получим такую ошибку:
EL1025E:(pos 4): The collection has '0' elements, index '0' is invalid
Мы можем изменить поведение синтаксического анализатора, чтобы позволить ему автоматически создавать элементы, если указанный индекс равен null ( 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 .