Выражения-литералы

Поддерживаются следующие типы литеральных выражений: строки, числовые значения (int, real, hex), булевы и нулевые. Строки разделяются одинарными кавычками. Чтобы заключить саму кавычку в строку, используйте два символа одинарной кавычки.

В следующем листинге показано простое использование литералов. Обычно они используются не изолированно, как в данном случае, а как часть более сложного выражения – например, при использовании литерала с одной стороны оператора логического сравнения.

Java
ExpressionParser parser = new SpelExpressionParser();
// имеет значение "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
// оказывается 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
Object nullValue = parser.parseExpression("null").getValue();
Kotlin
val parser = SpelExpressionParser()
// имеет значение "Hello World"
val helloWorld = parser.parseExpression("'Hello World'").value as String
val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double
// оказывается 2147483647
val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int
val trueValue = parser.parseExpression("true").value as Boolean
val nullValue = parser.parseExpression("null").value

Числа поддерживают использование отрицательного знака, экспоненциальной формы представления и десятичных точек. По умолчанию вещественные числа парсятся с помощью Double.parseDouble().

Свойства, массивы, списки, ассоциативные массивы и индексаторы

Осуществлять навигацию с помощью ссылок на свойства просто. Для этого используйте точку, чтобы указать значение вложенного свойства. Экземпляры класса Inventor, pupin и tesla, были заполнены данными. Чтобы перемещаться "вниз" по графу объектов и получить год рождения Теслы и город рождения Пупина, мы используем следующие выражения:

Java
// оказывается 1856
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
Kotlin
// оказывается 1856
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int
val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String

Нечувствительность к регистру допускается для первой буквы имен свойств. Таким образом, выражения в приведенном выше примере можно записать как Birthdate.Year + 1900 и PlaceOfBirth.City, соответственно. Кроме того, к свойствам можно обращаться через вызовы методов – например, getPlaceOfBirth().getCity() вместо placeOfBirth.city.

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

Java
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// Массив изобретений
// имеет значение "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
        context, tesla, String.class);
// Список членов
// имеет значение "Nikola Tesla".
String name = parser.parseExpression("members[0].name").getValue(
        context, ieee, String.class);
// Список членов
// имеет значение "Wireless communication"
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
        context, ieee, String.class);
Kotlin
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
// Массив изобретений
// имеет значение "Induction motor"
val invention = parser.parseExpression("inventions[3]").getValue(
        context, tesla, String::class.java)
// Список членов
// имеет значение "Nikola Tesla".
val name = parser.parseExpression("members[0].name").getValue(
        context, ieee, String::class.java)
// Список членов
// имеет значение "Wireless communication"
val invention = parser.parseExpression("members[0].inventions[6]").getValue(
        context, ieee, String::class.java)

Содержимое ассоциативных массивов можно получить, указав литеральное значение ключа в скобках. В следующем примере, поскольку ключи для ассоциативного массива officers являются строками, мы можем указать строковые литералы:

Java
// Офицерский словарь
Inventor pupin = parser.parseExpression("officers['president']").getValue(
        societyContext, Inventor.class);
// имеет значение "Idvor"
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
        societyContext, String.class);
// установка значений
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
        societyContext, "Croatia");
Kotlin
// Офицерский словарь
val pupin = parser.parseExpression("officers['president']").getValue(
        societyContext, Inventor::class.java)
// имеет значение "Idvor"
val city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
        societyContext, String::class.java)
// установка значений
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
        societyContext, "Croatia")

Встраиваемые списки

Вы можете непосредственно выражать списки в выражении, используя нотацию {}.

Java
// определяется как Java-список, содержащий четыре числа
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
Kotlin
// определяется как Java-список, содержащий четыре числа
val numbers = parser.parseExpression("{1,2,3,4}").getValue(context) as List<*>
val listOfLists = parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context) as List<*>

{} сама по себе означает пустой список. По соображениям производительности, если список полностью состоит из фиксированных литералов, для представления выражения создается список констант (вместо того, чтобы строить новый список при каждом вычислении).

Встраиваемые Map

Вы также можете напрямую выразить Map в выражении, используя нотацию {key:value}. В следующем примере показано, как это сделать:

Java
// определяется как Java Map, содержащий две записи
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);
Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
Kotlin
// определяется как Java Map, содержащий две записи
val inventorInfo = parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context) as Map<*, *>
val mapOfMaps = parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context) as Map<*, *>

{:} сама по себе означает пустой Map. По соображениям производительности, если Map сама состоит из фиксированных литералов или других вложенных константных структур (списков или Map), для представления выражения создается Map констант (а не строится новый Map при каждом вычислении). Заключать ключи Map в кавычки необязательно (если только ключ не содержит точку (.)). В приведенных выше примерах не используются кавычки.

Построение массивов

Вы можете создавать массивы, используя привычный синтаксис Java, опционально указывая инициализатор, чтобы массив заполнялся во время построения. В следующем примере показано, как это сделать:

Java
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Массив с инициализатором
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Многомерный массив
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
Kotlin
val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray
// Массив с инициализатором
val numbers2 = parser.parseExpression("new int[]{1,2,3}").getValue(context) as IntArray
// Многомерный массив
val numbers3 = parser.parseExpression("new int[4][5]").getValue(context) as Array<IntArray>

В настоящее время нельзя указывать инициализатор при создании многомерного массива.

Методы

Вы можете вызывать методы, используя типичный синтаксис программирования Java. Вы также можете вызвать методы для литералов. Также поддерживаются переменные аргументы. В следующих примерах показано, как вызывать методы:

Java
// строковый литерал, имеющий значение "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);
// оказывается true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean.class);
Kotlin
// строковый литерал, имеющий значение "bc"
val bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String::class.java)
// оказывается true
val isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean::class.java)

Операторы

Язык выражений Spring Expression Language поддерживает следующие виды операторов:

  • Реляционные операторы

  • Логические операторы

  • Математические операторы

  • Оператор присваивания

Реляционные операторы

Операторы отношений (равно (equal), не равно (not equal), меньше чем (less than), меньше чем или равно ( less than or equal), больше чем ( greater than) и больше чем или равно (greater than or equal)) поддерживаются при помощи стандартной нотации оператора. В следующем листинге приведены несколько примеров операторов:

Java
// оказывается true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
// оказывается false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
// оказывается true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
Kotlin
// оказывается true
val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java)
// оказывается false
val falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean::class.java)
// оказывается true
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)

Сравнения "больше чем" и "меньше чем" с null подчиняются простому правилу: null считается ничем (это НЕ ноль). Как следствие, любое другое значение всегда больше null(X > null всегда true) и никакое иное значение никогда не меньше чем ничто (X < null всегда false).

Если вы предпочитаете числовые сравнения, избегайте null сравнений на основе чисел в пользу сравнений с нулем (например, X > 0 или X < 0).

В дополнение к стандартным операторам отношений SpEL поддерживает оператор instanceof и оператор matches на основе регулярных выражений. В следующем списке приведены примеры обоих вариантов:

Java
// оказывается false
boolean falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean.class);
// оказывается true
boolean trueValue = parser.parseExpression(
        "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// оказывается false
boolean falseValue = parser.parseExpression(
        "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
Kotlin
// оказывается false
val falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean::class.java)
// оказывается true
val trueValue = parser.parseExpression(
        "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
// оказывается false
val falseValue = parser.parseExpression(
        "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
Будьте осторожны с примитивными типами, поскольку они сразу же упаковываются в свои типы-обёртки. Например, 1 instanceof T(int) оказывается false, а 1 instanceof T(Integer) оказывается true, как и ожидалось.

Каждый символьный оператор также может быть указан в виде чисто буквенного эквивалента. Это позволяет избежать проблем, когда используемые символы имеют особое значение для типа документа, в который встраивается выражение (например, в XML-документ). Текстовые эквиваленты таковы:

  • lt (<)

  • gt (>)

  • le (<=)

  • ge (>=)

  • eq (==)

  • ne (!=)

  • div (/)

  • mod (%)

  • not (!).

Все текстовые операторы не чувствительны к регистру.

Логические операторы

SpEL поддерживает следующие логические операторы:

  • and (&&)

  • or (||)

  • not (!)

В следующем примере показано, как использовать логические операторы:

Java
// -- AND --
// оказывается false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
// оказывается true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- OR --
// оказывается true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
// оказывается true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- NOT --
// оказывается false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
Kotlin
// -- AND --
// оказывается false
val falseValue = parser.parseExpression("true and false").getValue(Boolean::class.java)
// оказывается true
val expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
// -- OR --
// оказывается true
val trueValue = parser.parseExpression("true or false").getValue(Boolean::class.java)
// оказывается true
val expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
// -- NOT --
// оказывается false
val falseValue = parser.parseExpression("!true").getValue(Boolean::class.java)
// -- AND and NOT --
val expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"
val falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)

Математические операторы

Оператор сложения (+) можно использовать как для чисел, так и для строк. Операторы вычитания (-), умножения (*) и деления (/) можно использовать только для чисел. Также можно использовать операторы взятия остатка (%) и экспоненциального роста (^) для чисел. Применяется стандартный приоритет операторов. В следующем примере показаны используемые математические операторы:

Java
// Сложение
int two = parser.parseExpression("1 + 1").getValue(Integer.class);  // 2
String testString = parser.parseExpression(
        "'test' + ' ' + 'string'").getValue(String.class);  // 'test string'
// Вычитание
int four = parser.parseExpression("1 - -3").getValue(Integer.class);  // 4
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class);  // -9000
// Умножение
int six = parser.parseExpression("-2 * -3").getValue(Integer.class);  // 6
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class);  // 24.0
// Деление
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class);  // -2
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class);  // 1.0
// Взятие остатка
int three = parser.parseExpression("7 % 4").getValue(Integer.class);  // 3
int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class);  // 1
// Приоритет операторов
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class);  // -21
Kotlin
// Сложение
val two = parser.parseExpression("1 + 1").getValue(Int::class.java)  // 2
val testString = parser.parseExpression(
        "'test' + ' ' + 'string'").getValue(String::class.java)  // 'test string'
// Вычитание
val four = parser.parseExpression("1 - -3").getValue(Int::class.java)  // 4
val d = parser.parseExpression("1000.00 - 1e4").getValue(Double::class.java)  // -9000
// Умножение
val six = parser.parseExpression("-2 * -3").getValue(Int::class.java)  // 6
val twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double::class.java)  // 24.0
// Деление
val minusTwo = parser.parseExpression("6 / -3").getValue(Int::class.java)  // -2
val one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double::class.java)  // 1.0
// Взятие остатка
val three = parser.parseExpression("7 % 4").getValue(Int::class.java)  // 3
val one = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java)  // 1
// Приоритет операторов
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java)  // -21

Оператор присваивания

Чтобы задать свойство, используйте оператор присваивания (=). Обычно это делается внутри вызова setValue, но может быть сделано и внутри вызова getValue. В следующем листинге показаны оба способа использования оператора присваивания:

Java
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");
// альтернатива
String aleks = parser.parseExpression(
        "name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
Kotlin
val inventor = Inventor()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic")
// альтернатива
val aleks = parser.parseExpression(
        "name = 'Aleksandar Seovic'").getValue(context, inventor, String::class.java)

Типы

Вы можете использовать специальный оператор T для указания экземпляра java.lang.Class (тип). Статические методы также вызываются с помощью этого оператора. StandardEvaluationContext использует TypeLocator для поиска типов, а StandardTypeLocator (который может быть заменен) построен с учетом особенностей пакета java.lang. Это означает, что ссылки T() на типы из пакета java.lang не нужно полностью уточнять, однако все остальные ссылки на типы должны быть уточнены. В следующем примере показано, как использовать оператор T:

Java
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
boolean trueValue = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean.class);
Kotlin
val dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class::class.java)
val stringClass = parser.parseExpression("T(String)").getValue(Class::class.java)
val trueValue = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean::class.java)

Конструкторы

Можно вызывать конструкторы с помощью оператора new. Вам нужно использовать полностью уточненное имя класса для всех типов, кроме тех, которые находятся в пакете java.lang(Integer, Float, String и так далее). В следующем примере показано, как использовать оператор new для вызова конструкторов:

Java
Inventor einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor.class);
// создаем новый экземпляр Inventor в методе add() List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor(
            'Albert Einstein', 'German'))").getValue(societyContext);
Kotlin
val einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor::class.java)
// создаем новый экземпляр Inventor в методе add() List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
        .getValue(societyContext)

Переменные

Вы можете ссылаться на переменные в выражении, используя синтаксис #variableName. Переменные устанавливаются с помощью метода setVariable в реализации EvaluationContext.

Допустимые имена переменных должны состоять из одного или нескольких из указанных далее поддерживаемых символов.

  • буквы: От A до Z и от a до z

  • цифры: от 0 до 9

  • подчеркивание: _

  • знак доллара: $

В следующем примере показано, как использовать переменные.

Java
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("name = #newName").getValue(context, tesla);
System.out.println(tesla.getName())  // "Mike Tesla"
Kotlin
val tesla = Inventor("Nikola Tesla", "Serbian")
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
context.setVariable("newName", "Mike Tesla")
parser.parseExpression("name = #newName").getValue(context, tesla)
println(tesla.name)  // "Mike Tesla"

Переменные #this и #root

Переменная #this всегда определена и ссылается на текущий объект вычисления (относительно которого разрешаются неквалифицированные ссылки). Переменная #root всегда определена и ссылается на объект корневого контекста. Хотя #this может меняться по мере вычисления компонентов выражения, #root всегда относится к корню. В следующих примерах показано, как использовать переменные #this и #root:

Java
// создаем массив целых чисел
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// создаем синтаксический анализатор и задаем переменную "primes" как массив целых чисел
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
context.setVariable("primes", primes);
// все простые числа > 10 из списка (используя выборку ?{...})
// оказывается [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
        "#primes.?[#this>10]").getValue(context);
Kotlin
// создаем массив целых чисел
val primes = ArrayList<Int>()
primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17))
// создаем синтаксический анализатор и задаем переменную "primes" как массив целых чисел
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataAccess()
context.setVariable("primes", primes)
// все простые числа > 10 из списка (используя выборку ?{...})
// оказывается [11, 13, 17]
val primesGreaterThanTen = parser.parseExpression(
        "#primes.?[#this>10]").getValue(context) as List<Int>

Функции

Вы можете расширить SpEL, зарегистрировав определяемые пользователем функции, которые могут быть вызваны в строке выражения. Функция регистрируется через EvaluationContext. В следующем примере показано, как зарегистрировать функцию, определяемую пользователем:

Java
Method method = ...;
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
Kotlin
val method: Method = ...
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("myFunction", method)

Например, рассмотрим следующий служебный метод, который инвертирует строку:

Java
public abstract class StringUtils {
    public static String reverseString(String input) {
        StringBuilder backwards = new StringBuilder(input.length());
        for (int i = 0; i < input.length(); i++) {
            backwards.append(input.charAt(input.length() - 1 - i));
        }
        return backwards.toString();
    }
}
Kotlin
fun reverseString(input: String): String {
    val backwards = StringBuilder(input.length)
    for (i in 0 until input.length) {
        backwards.append(input[input.length - 1 - i])
    }
    return backwards.toString()
}

Затем можно зарегистрировать и использовать предыдущий метод, как показано в следующем примере:

Java
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
        StringUtils.class.getDeclaredMethod("reverseString", String.class));
String helloWorldReversed = parser.parseExpression(
        "#reverseString('hello')").getValue(context, String.class);
Kotlin
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("reverseString", ::reverseString::javaMethod)
val helloWorldReversed = parser.parseExpression(
        "#reverseString('hello')").getValue(context, String::class.java)

Ссылки на бин

Если контекст вычисления был сконфигурирован с помощью распознавателя бинов, вы сможете осуществлять поиск бинов в выражении с помощью символа @. В следующем примере показано, как это сделать:

Java
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// Это приведет к вызову resolve(context, "something") для MyBeanResolver во время вычисления
Object bean = parser.parseExpression("@something").getValue(context);
Kotlin
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())
// Это приведет к вызову resolve(context, "something") для MyBeanResolver во время вычисления
val bean = parser.parseExpression("@something").getValue(context)

Чтобы получить доступ к самому бину-фабрике, следует вместо имени бина поставить символ &. В следующем примере показано, как это сделать:

Java
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// Это приведет к вызову resolve(context,"&foo") для MyBeanResolver во время вычисления
Object bean = parser.parseExpression("&foo").getValue(context);
Kotlin
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())
// Это приведет к вызову resolve(context,"&foo") для MyBeanResolver во время вычисления
val bean = parser.parseExpression("&foo").getValue(context)

Тернарный оператор (If-Then-Else)

Можно использовать тернарный оператор для выполнения логики условного перехода if-then-else (если-то-иначе) внутри выражения. В следующем листинге приведен простой пример:

Java
String falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String.class);
Kotlin
val falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String::class.java)

В данном случае булево false приводит к возвращению строкового значения 'falseExp'. Далее следует более приближенный к практике пример:

Java
parser.parseExpression("name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
        "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";
String queryResultString = parser.parseExpression(expression)
        .getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
Kotlin
parser.parseExpression("name").setValue(societyContext, "IEEE")
societyContext.setVariable("queryName", "Nikola Tesla")
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"
val queryResultString = parser.parseExpression(expression)
        .getValue(societyContext, String::class.java)
// queryResultString = "Nikola Tesla is a member of the IEEE Society"

См. следующий раздел об Элвис-операторе для еще более короткого синтаксиса тернарного оператора.

Элвис-оператор

Элвис-оператор (Elvis operator) является сокращением синтаксиса тернарного оператора и используется в языке Groovy. При использовании синтаксиса тернарного оператора обычно приходится повторять переменную дважды, как показано в следующем примере:

String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");

Вместо этого вы можете использовать Элвис-оператора (назван так из-за сходства с прической Элвиса Пресли). В следующем примере показано, как использовать Элвис-оператор:

Java
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name);  // 'Unknown'
Kotlin
val parser = SpelExpressionParser()
val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java)
println(name)  // 'Unknown'

В следующем листинге показан более сложный пример:

Java
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Nikola Tesla
tesla.setName(null);
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Elvis Presley
Kotlin
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
val tesla = Inventor("Nikola Tesla", "Serbian")
var name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name)  // Nikola Tesla
tesla.setName(null)
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name)  // Elvis Presley

Вы можете использовать Элвис-оператор для применения значений по умолчанию в выражениях. Следующий пример показывает, как использовать Элвис-оператор в выражении, аннотированным @Value:

@Value("#{systemProperties['pop3.port'] ?: 25}")

Таким образом внедряется системное свойство pop3.port, если оно определено, или 25, если нет.

Оператор безопасной навигации

Оператор безопасной навигации используется для предотвращения NullPointerException и происходит из языка Groovy. Обычно, если есть ссылка на объект, то может понадобиться удостовериться, что она не является пустой, прежде чем обращаться к методам или свойствам объекта. Чтобы избежать этого, оператор безопасной навигации возвращает null, а не генерирует исключение. В следующем примере показано, как использовать оператор безопасной навигации:

Java
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city);  // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city);  // null - не генерирует NullPointerException!!!
Kotlin
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
val tesla = Inventor("Nikola Tesla", "Serbian")
tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan"))
var city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
println(city)  // Smiljan
tesla.setPlaceOfBirth(null)
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
println(city)  // null - не генерирует NullPointerException!!!

Выборка коллекций

Выборка (selection) – это мощная функция языка выражений, которая позволяет преобразовывать исходную коллекцию в другую коллекцию путем выборки из ее записей.

Для выборки используется синтаксис .?[selectionExpression]. Он фильтрует коллекцию и возвращает новую коллекцию, содержащую подмножество исходных элементов. Например, с помощью выборки можно легко получить список сербских изобретателей, как показано в следующем примере:

Java
List<Inventor> list = (List<Inventor>) parser.parseExpression(
        "members.?[nationality == 'Serbian']").getValue(societyContext);
Kotlin
val list = parser.parseExpression(
        "members.?[nationality == 'Serbian']").getValue(societyContext) as List<Inventor>

Выборка поддерживается для массивов и всего, что реализует java.lang.Iterable или java.util.Map. Для списка или массива критерии выборки оцениваются по каждому отдельному элементу. Относительно ассоциативного массива критерии выборки оцениваются по каждому элементу ассоциативного массива (объекты Java типа Map.Entry). Каждая запись ассоциативного массива имеет key и value, доступные как свойства для использования при выборке.

Следующее выражение возвращает новый ассоциативный массив, состоящий из тех элементов исходного ассоциативного массива, в которых значение элемента меньше 27:

Java
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
Kotlin
val newMap = parser.parseExpression("map.?[value<27]").getValue()

Помимо возврата всех выбранных элементов, можно получать только первый или последний элемент. Для получения первого элемента, удовлетворяющего критериям выборки, синтаксис имеет вид .^[selectionExpression]. Чтобы получить последний элемент, удовлетворяющий критериям выборки, синтаксис принимает вид .$[selectionExpression].

Проекция коллекций

Проекция (projection) позволяет коллекции управлять вычислением подвыражения, а результатом является новая коллекция. Синтаксис для проекции имеет вид .![projectionExpression]. Например, предположим, что у нас есть список изобретателей, но нам нужен список городов, в которых они родились. По сути, мы хотим произвести вычисление 'placeOfBirth.city' для каждой записи в списке изобретателей. В следующем примере для этого используется проекция:

Java
// возвращает ['Smiljan', 'Idvor' ].
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
Kotlin
// возвращает ['Smiljan', 'Idvor' ].
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") as List<*>

Проекция поддерживается для массивов и всего, что реализует java.lang.Iterable или java.util.Map. При использовании ассоциативного массива для управления проекцией выражение проекции вычисляется по каждой записи в этом ассоциативном массиве (представленной в виде Map.Entry на языке Java). Результатом проекции по ассоциативному массиву является список, состоящий из вычисления выражения проекции для каждой записи ассоциативного массива.

Шаблонизация выражений

Шаблоны выражений позволяют смешивать буквенный текст с одним или несколькими блоками вычислений. Каждый блок вычислений разграничен символами префикса и суффикса, которые вы можете определить. Обычно в качестве разделителей используется #{ }, как показано в следующем примере:

Java
String randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        new TemplateParserContext()).getValue(String.class);
// получается "случайное число равно 0.7038186818312008"
Kotlin
val randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        TemplateParserContext()).getValue(String::class.java)
// получается "случайное число равно 0.7038186818312008"

Вычисление строки осуществляется путем конкатенации буквенного текста 'random number is ' с результатом вічисления выражения внутри разделителя #{ } (в данном случае результат вызова метода random()). Второй аргумент метода parseExpression() имеет тип ParserContext. Интерфейс ParserContext используется для того, чтобы влиять на то, как будет синтаксически разобрано выражение, чтобы обеспечить функциональность шаблонизации выражений. Далее следует определение TemplateParserContext:

Java
public class TemplateParserContext implements ParserContext {
    public String getExpressionPrefix() {
        return "#{";
    }
    public String getExpressionSuffix() {
        return "}";
    }
    public boolean isTemplate() {
        return true;
    }
}
Kotlin
class TemplateParserContext : ParserContext {
    override fun getExpressionPrefix(): String {
        return "#{"
    }
    override fun getExpressionSuffix(): String {
        return "}"
    }
    override fun isTemplate(): Boolean {
        return true
    }
}