Literal expressions
The following types of literal expressions are supported: strings, numeric values (int, real, hex), boolean and null. Strings are separated by single quotes. To enclose the quote itself in a string, use two single quote characters.
The following listing shows a simple use of literals. They are typically not used in isolation, as in this case, but as part of a more complex expression - for example, when using a literal on one side of a logical comparison operator.
ExpressionParser parser = new SpelExpressionParser();
// has the value "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
// turns out to be 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
Object nullValue = parser.parseExpression("null").getValue();
val parser = SpelExpressionParser()
// has the value "Hello World"
val helloWorld = parser.parseExpression("'Hello World'").value as String
val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double
// turns out 2147483647
val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int
val trueValue = parser.parseExpression("true").value as Boolean
val nullValue = parser.parseExpression("null").value
Numbers support the use of negative sign, scientific notation, and decimal points. By default, real numbers
are parsed using Double.parseDouble()
.
Properties, arrays, lists, associative arrays, and indexers
Navigate using links to properties are simple. To do this, use a period to indicate the value of
the attached property. The Inventor
class instances, pupin
and tesla
, have
been populated with data. To move "down" through the object graph and get Tesla's birth year and Pupin's birth city,
we use the following expressions:
// turns out to be 1856
int year = ( Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
// turns out to be 1856
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int
val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String
Case insensitiveness is allowed for the first letter of property names. So
the expressions in the above example can be written as Birthdate.Year + 1900
and PlaceOfBirth.City
,
respectively. In addition, properties can be accessed through method calls - for example, getPlaceOfBirth().getCity()
instead of placeOfBirth.city
.
The contents of arrays and lists can be obtained using square bracket notation, as shown in the following example:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// Array of inventions
// has the value "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
context, tesla, String.class);
// Member list
// has the value "Nikola Tesla".
String name = parser.parseExpression("members[0].name").getValue(
context, ieee, String.class);
// List of members
// has the value "Wireless communication"
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
context, ieee, String.class);
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
// Array of inventions
// matters "Induction motor"
val invention = parser.parseExpression("inventions[3]").getValue(
context, tesla, String::class.java)
// Member list
// has the value "Nikola Tesla".
val name = parser.parseExpression("members[0].name").getValue(
context, ieee, String::class.java)
// List of members
// has the value "Wireless communication"
val invention = parser.parseExpression(" members[0].inventions[6]").getValue(
context, ieee, String::class.java)
The contents of associative arrays can be obtained by specifying the literal value of the key in
parentheses. In the following example, since the keys for the associative array officers
are strings,
we can specify string literals:
// Officer dictionary
Inventor pupin = parser.parseExpression("officers['president']").getValue(
societyContext, Inventor.class);
// has the value "Idvor"
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
societyContext, String.class);
// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
societyContext, "Croatia");
// Officer dictionary
val pupin = parser.parseExpression("officers['president']").getValue(
societyContext, Inventor::class.java)
// has value "Idvor"
val city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
societyContext, String::class.java)
// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
societyContext, "Croatia")
Embeddable lists
You can directly express lists in an expression using the {}
notation.
// defined as a Java list containing four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
// defined as a Java list containing four numbers
val numbers = parser.parseExpression("{1,2,3,4}" ).getValue(context) as List<*>
val listOfLists = parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context) as List<*>
{}
itself means an empty list. For performance reasons, if the list consists entirely of fixed
literals, a list of constants is created to represent the expression (rather than constructing a new list each time
it is evaluated).
Inline Maps
You can also express Map directly in an expression using the {key:value}
notation. The following example shows how to do this:
// is defined as a Java Map containing two entries
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);
// is defined as a Java Map containing two entries
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<*, *>
{:}
itself means an empty Map. For performance reasons, if the Map itself consists of fixed
literals or other nested constant structures (lists or Maps), a Map of constants is created to represent the
expression (rather than constructing a new Map each time it is evaluated). It is not necessary to enclose Map keys
in quotes (unless the key contains a period (.)). The examples above do not use quotes.
Constructing Arrays
You can create arrays using familiar Java syntax, optionally specifying an initializer so that the array is populated during construction. The following example shows how to do this:
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Multidimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray
// Array with initializer
val numbers2 = parser.parseExpression("new int[]{1,2,3}").getValue(context) as IntArray
// Multidimensional array
val numbers3 = parser.parseExpression("new int[4][5]").getValue( context) as Array<IntArray>
Currently, you cannot specify an initializer when creating a multidimensional array.
Methods
You can call methods using typical Java programming syntax. You can also call methods on literals. Variable arguments are also supported. The following examples show how to call methods:
// String literal having the value "bc"
String bc = parser.parseExpression("'abc'. substring(1, 3)").getValue(String.class);
// turns out to be true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean.class);
// string literal having the value "bc"
val bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String::class. java)
// turns out to be true
val isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean::class.java)
Operators
The Spring Expression Language supports the following types of operators:
Relational operators
Logical operators
Mathematical operators
Assignment operator
Relational operators
Relational operators (equal, not equal, less than, less than or equal, greater than, and greater than greater than or equal) are supported using standard operator notation. The following listing provides several example operators:
// turns out to be true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean. class);
// turns out to be false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
// turns out to be true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
// turns out to be true
val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java)
// turns out to be false
val falseValue = parser.parseExpression( "2 < -5.0").getValue(Boolean::class.java)
// turns out to be true
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)
Comparisons of "greater than" and "less than" with null
obey
a simple rule: null
is considered nothing (it is NOT zero). As a consequence, any other value is always
greater than null(X > null
is always true
) and no other value is ever less than nothing
(X < null
code> is always false
).
If you prefer numeric comparisons, avoid null
number-based comparisons in favor of null-based
comparisons (e.g. X > 0
or X < 0
).
In addition to the standard
relational operators, SpEL supports the instanceof
operator and a matches
operator based
on regular expressions. The following list provides examples of both options:
// turns out to be false
boolean falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
// turns out to be true
boolean trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// turns out to be false
boolean falseValue = parser.parseExpression(
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// turns out to be false
val falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean::class.java)
// turns out to be true
val trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
// turns out to be false
val falseValue = parser.parseExpression(
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue (Boolean::class.java)
1 instanceof T(int)
turns out to be false
, and 1
instanceof T(Integer)
turns out to be true
, like as expected.
Each character operator can also be specified as a purely literal equivalent. This avoids problems when the characters used have a special meaning for the type of document in which the expression is embedded (for example, an XML document). The text equivalents are:
lt
(<
)gt
(>
)le
(<=
)ge
(>=
)eq
(==
)ne
(!=
)div
(/
)mod
(%
)not
(!
).
All text operators are case insensitive.
Logical operators
SpEL supports the following logical operators:
and
(&&
)or
(||
)not
(!
)
The following example shows how to use logical operators:
// -- AND --
// turns out to be false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
// turns out to be true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- OR --
// turns out to be true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
// turns out to be true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- NOT --
// turns out to be 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);
// -- AND --
// turns out to be false
val falseValue = parser.parseExpression("true and false").getValue(Boolean::class.java)
// turns out to be true
val expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
// -- OR --
// turns out to be true
val trueValue = parser.parseExpression("true or false").getValue(Boolean::class.java)
// turns out to be true
val expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
// -- NOT --
// turns out to be 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)
Math Operators
The addition operator (+
) can be used for both numbers and strings. The
subtraction (-
), multiplication (*
), and division (/
) operators can only be
used for numbers. You can also use the remainder (%
) and exponential growth (^
) operators
for numbers. Standard operator precedence applies. The following example shows the mathematical operators used:
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
String testString = parser.parseExpression(
"'test' + ' ' + 'string'").getValue(String.class);// 'test string'
// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
// Division
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0
// Taking the remainder
int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3
int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1
// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
// Addition
val two = parser.parseExpression("1 + 1" ).getValue(Int::class.java) // 2
val testString = parser.parseExpression(
"'test' + ' ' + 'string'").getValue(String::class.java) // 'test string'
// Subtraction
val four = parser.parseExpression("1 - -3").getValue(Int::class.java) // 4
val d = parser.parseExpression("1000.00 - 1e4").getValue(Double::class .java) // -9000
// Multiplication
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
// Division 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
// Taking the remainder
val three = parser.parseExpression("7 % 4").getValue(Int::class. java) // 3
val one = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java) // 1
// Operator precedence
val minusTwentyOne = parser.parseExpression("1+2-3 *8").getValue(Int::class.java) // -21
Assignment operator
To set a property, use the assignment operator (=
). This is usually
done inside a setValue
call, but can also be done inside a getValue
call. The following
listing shows both ways to use the assignment operator:
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");
// alternative to
String aleks = parser.parseExpression(
"name = 'Aleksandar Seovic' ").getValue(context, inventor, String.class);
val inventor = Inventor()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic")
// alternative
val aleks = parser.parseExpression(
"name = 'Aleksandar Seovic'").getValue(context, inventor, String::class.java)
Types
You can use the special operator T
to specify an instance of
java.lang.Class
(type). Static methods are also called using this operator. StandardEvaluationContext
uses TypeLocator
to look up types, and StandardTypeLocator
(which can be replaced) is built to take advantage of the
java.lang
package. This means that T()
references to types from the java.lang
package do not need to be fully qualified, but all other type references must be qualified. The following example
shows how to use the T
operator:
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);
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)
Constructors
You can call constructors using the new
operator. You need to use the fully
qualified class name for all types except those in the java.lang(Integer
, Float
, String
package and so on Further). The following example shows how to use the new
operator to call
constructors:
Inventor einstein = p.parseExpression(
"new org. spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
// create a new Inventor instance in the add() method List
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor(
'Albert Einstein', 'German'))").getValue(societyContext );
val einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor::class.java)
// create a new Inventor instance in the add() method List
p.parseExpression(
"Members.add(new org.spring .samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
.getValue(societyContext)
Variables
You can refer to variables in an expression using the syntax #variableName
.
Variables are set using the setVariable
method in the EvaluationContext
implementation.
Valid variable names must consist of one or more of the following supported characters.
letters:
A
toZ
anda
toz
numbers: from
0
to9
underscore:
_
dollar sign:
$
The following example shows how to use variables.
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"
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"
Variables #this
and #root
The variable #this
is always defined
and refers to the current calculation object (relative to which unqualified references are resolved). The #root
variable is always defined and refers to the root context object. Although #this
can change as the
expression's components are evaluated, #root
always refers to the root. The following examples show how
to use the #this
and #root
variables:
// create array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// create a parser and set the "primes" variable as an array of integers
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
context.setVariable("primes", primes);
// all prime numbers > 10 from the list (using selection ?{...})
// turns out to be [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
"#primes.?[#this>10]").getValue(context);
// create an array of integers
val primes = ArrayList<Int>() primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17))
// create a parser and set the variable "primes" as an array of integers
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataAccess()
context.setVariable("primes", primes)
// all primes > 10 from the list (using selection ?{...})
// turns out to be [11, 13, 17]
val primesGreaterThanTen = parser.parseExpression(
"#primes.?[#this>10]").getValue(context) as List< ;Int>
Functions
You can extend SpEL by registering user-defined functions that can be called on the
expression line. The function is registered via EvaluationContext
. The following example shows how to
register a user-defined function:
Method method = ...;
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
val method: Method = ...
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("myFunction", method)
For example, consider the following utility method that inverts line:
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();
}
}
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()
}
You can then register and use the previous method, as shown in the following example:
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);
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("reverseString", ::reverseString::javaMethod)
val helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String::class.java)
Bean references
If the evaluation context has been configured with a bean resolver, you will be able to search for beans in an
expression using the @
symbol. The following example shows how to do this:
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will cause resolve(context, "something") to be called on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())
// This will call resolve(context, "something ") for MyBeanResolver during evaluation
val bean = parser.parseExpression("@something").getValue(context)
To access the bean itself - factory, you should replace the bean name with the symbol &
. The
following example shows how to do this:
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will cause resolve(context,"&foo") to be called on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())
// This will call resolve(context ,"&foo") for MyBeanResolver during evaluation
val bean = parser.parseExpression("&foo").getValue(context)
Ternary (If-Then-Else) operator
You can use the ternary operator to perform if-then-else conditional jump logic within an expression. The following listing shows a simple example:
String falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String.class);
val falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String::class.java)
In this case, the boolean false
results in returning the string value 'falseExp'
. The
following is an example closer to practice:
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"
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"
See. See the following section on the Elvis operator for even shorter ternary operator syntax.
Elvis operator
The Elvis operator is a shorthand for ternary operator syntax and is used in the Groovy language. When using ternary operator syntax, you typically have to repeat the variable twice, as shown in the following example:
String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");
You can use the Elvis operator instead (so named because of the similarity with Elvis Presley's hair). The following example shows how to use the Elvis operator:
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name); // 'Unknown'
val parser = SpelExpressionParser()
val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java)
println(name) // 'Unknown'
The following listing shows a more complex example:
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
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
You can use the Elvis operator to apply default values in expressions. The
following example shows how to use the Elvis operator in an expression annotated with @Value
:
@Value("#{systemProperties['pop3.port'] ?: 25}")
This injects the system property pop3.port
if defined, or 25 if not.
Safe navigation operator
The Safe Navigation Operator is used to prevent NullPointerException
and comes from the language Groovy.
Typically, if you have a reference to an object, you may want to ensure that it is not empty before accessing
the object's methods or properties. To avoid this, the safe navigation operator returns null rather than
throwing an exception. The following example shows how to use the safe navigation operator:
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 - does not throw NullPointerException!!!
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 - does not throw NullPointerException!!!
Selection of collections
Selection is A powerful expression language feature that allows you to transform a source collection into another collection by selecting from its records.
Selecting uses the syntax .?[selectionExpression]
. It filters the collection and returns a new
collection containing a subset of the original elements. For example, using sampling, you can easily obtain a
list of Serbian inventors, as shown in the following example:
List<Inventor> list = (List<Inventor>) parser.parseExpression(
"members.?[nationality == 'Serbian']").getValue(societyContext);
val list = parser.parseExpression(
"members.?[nationality == 'Serbian']").getValue(societyContext) as List<Inventor>
Sampling is supported for arrays and anything that implements java.lang.Iterable
or
java.util.Map
.
For a list or array, the selection criteria are evaluated on each individual element. With respect to an
associative array, the selection criteria are evaluated on each element of the associative array (Java objects
of type Map.Entry
). Each associative array entry has a key
and a value
,
available as properties for use when fetching.
The following expression returns a new associative array consisting of those elements of the original associative array in which the element value is less than 27:
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
val newMap = parser.parseExpression("map.?[value<27]").getValue()
In addition to returning all selected elements, you can only return the first or last element. To get the first
element that matches the selection criteria, the syntax is .^[selectionExpression]
. To get the last
element that matches the selection criteria, the syntax is .$[selectionExpression]
.
Projection of collections
Projection allows a collection to control the evaluation of a subexpression, and the result is a new collection.
The syntax for projection is .![projectionExpression]
. For example, let's say we have a list of
inventors, but we want a list of the cities in which they were born. Essentially, we want to compute
'placeOfBirth.city' for each entry in the list of inventors. The following example uses a projection to do this:
// returns ['Smiljan', 'Idvor' ].
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
// returns ['Smiljan', 'Idvor' ].
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") as List<*>
Projection is supported for arrays and anything that implements java.lang.Iterable
or java.util.Map
.
When you use an associative array to control a projection, the projection expression is evaluated over each
entry in that associative array (represented as Map.Entry
in Java). The result of a projection over
an associative array is a list consisting of evaluating the projection expression for each entry in the
associative array.
Expression templating
Expression templates allow you to mix literal text with one or more blocks of calculations. Each block of
calculations is delimited by prefix and suffix characters that you can define. Typically, #{ }
are
used as delimiters, as shown in the following example:
String randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class);
// it turns out "the random number is 0.7038186818312008"
val randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
TemplateParserContext()).getValue(String::class.java)
// it turns out "the random number is 0.7038186818312008"
The string is calculated by concatenating the literal text 'random number is '
with the result of
calculating the expression inside the delimiter #{ }
(in this case, the result of calling the
random( )
). The second argument of the parseExpression()
method is of type ParserContext
.
The ParserContext
interface is used to influence how an expression is parsed to provide expression
templating functionality. The following is the definition of TemplateParserContext
:
public class TemplateParserContext implements ParserContext {
public String getExpressionPrefix() {
return "#{";
}
public String getExpressionSuffix() {
return "}";
}
public boolean isTemplate() {
return true;
}
}
class TemplateParserContext : ParserContext {
override fun getExpressionPrefix(): String {
return "#{"
}
override fun getExpressionSuffix(): String {
return "}"
}
override fun isTemplate(): Boolean {
return true
}
}