Руководство по языку Spring Expression

1. Обзор

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

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

| ================================================= ======= | Тип | Операторы | Арифметика | , -, ** ,/,%, ^, div, mod | Relational | <,>, ==,! =, ⇐,> =, lt, gt, eq, ne, le, ge | Logical | и, или нет,

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 мы не можем использовать операторы, содержащие угловые скобки ( < , ⇐, > , > = ). Вместо этого мы можем использовать 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. Использование Regex в 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. Доступ к объектам List и Map

**

С помощью SpEL мы можем получить доступ к содержимому любого Map или List в контексте. Мы создадим новый компонент workersHolder , который будет хранить информацию о некоторых работниках и их зарплатах в 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. Использовать в весенней конфигурации

**

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

Взгляните на боб someCar . В полях engine и horsePower someCar используются выражения, которые являются ссылками bean-компонентов на поля engine bean и 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
    }

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

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

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

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> wantedResultType) , в котором мы можем предоставить желаемый тип класса, который мы хотим вернуть. Обратите внимание, что EvaluationException будет выдано, если возвращаемое значение не может быть приведено к desiredResultType :

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. Использование 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");

Получившийся автомобильный объект будет иметь model « Other model », который был изменен с « 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 или для написания парсеров для выполнения более общих задач в любом приложении.

Примеры кода в этой статье доступны в linked GitHub хранилище .