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 Listworkers = 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 Listcars = 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 .