Рубрики
Без рубрики

Руководство по языку выражений Spring

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

Автор оригинала: baeldung.

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 .