diff --git a/spring-framework-reference/src/expressions.xml b/spring-framework-reference/src/expressions.xml
index 8dd4b4f745c..7bb8062d9c3 100644
--- a/spring-framework-reference/src/expressions.xml
+++ b/spring-framework-reference/src/expressions.xml
@@ -1,1209 +1,1207 @@
-
-
-
- Spring Expression Language (SpEL)
-
-
- Introduction
-
- The Spring Expression Language (SpEL for short) is a powerful
- expression language that supports querying and manipulating an object
- graph at runtime. The language syntax is similar to Unified EL but offers
- additional features, most notably method invocation and basic string
- templating functionality.
-
- While there are several other Java expression languages available,
- OGNL, MVEL, and JBoss EL, to name a few, the Spring Expression Language
- was created to provide the Spring community with a single well supported
- expression language that can be used across all the products in the Spring
- portfolio. Its language features are driven by the requirements of the
- projects in the Spring portfolio, including tooling requirements for code
- completion support within the eclipse based SpringSource Tool Suite. That
- said, SpEL is based on a technology agnostic API allowing other
- expression language implementations to be integrated should the need
- arise.
-
- While SpEL serves as the foundation for expression evaluation within
- the Spring portfolio, it is not directly tied to Spring and can be used
- independently. In order to be self contained, many of the examples in this
- chapter use SpEL as if it were an independent expression language. This
- requires creating a few bootstrapping infrastructure classes such as the
- parser. Most Spring users will not need to deal with this infrastructure
- and will instead only author expression strings for evaluation. An example
- of this typical use is the integration of SpEL into creating XML or
- annotated based bean definitions as shown in the section Expression support for defining bean
- definitions.
-
- This chapter covers the features of the expression language, its
- API, and its language syntax. In several places an Inventor and Inventor's
- Society class are used as the target objects for expression evaluation.
- These class declarations and the data used to populate them are listed at
- the end of the chapter.
-
-
-
- Feature Overview
-
- The expression language supports the following functionality
-
-
-
- Literal expressions
-
-
-
- Boolean and relational operators
-
-
-
- Regular expressions
-
-
-
- Class expressions
-
-
-
- Accessing properties, arrays, lists, maps
-
-
-
- Method invocation
-
-
-
- Relational operators
-
-
-
- Assignment
-
-
-
- Calling constructors
-
-
-
- Ternary operator
-
-
-
- Variables
-
-
-
- User defined functions
-
-
-
- Collection projection
-
-
-
- Collection selection
-
-
-
- Templated expressions
-
-
-
-
-
- Expression Evaluation using Spring's Expression Interface
-
- This section introduces the simple use of SpEL interfaces and its
- expression language. The complete language reference can be found in the
- section Language
- Reference.
-
- The following code introduces the SpEL API to evaluate the literal
- string expression 'Hello World'.
-
- ExpressionParser parser = new SpelExpressionParser();
-Expression exp = parser.parseExpression("'Hello World'");
-String message = (String) exp.getValue();The value of the
- message variable is simply 'Hello World'.
-
- The SpEL classes and interfaces you are most likely to use are
- located in the packages org.springframework.expression
- and its sub packages and spel.support.
-
- The interface ExpressionParser is
- responsible for parsing an expression string. In this example the
- expression string is a string literal denoted by the surrounding single
- quotes. The interface Expression is
- responsible for evaluating the previously defined expression string. There
- are two exceptions that can be thrown,
- ParseException and
- EvaluationException when calling
- 'parser.parseExpression' and
- 'exp.getValue' respectively.
-
- SpEL supports a wide range of features, such as calling methods,
- accessing properties, and calling constructors.
-
- As an example of method invocation, we call the 'concat' method on
- the string literal.
-
- ExpressionParser parser = new SpelExpressionParser();
-Expression exp = parser.parseExpression("'Hello World'.concat('!')");
-String message = (String) exp.getValue();
-
- The value of message is now 'Hello World!'.
-
- As an example of calling a JavaBean property, the String property
- 'Bytes' can be called as shown below.
-
- ExpressionParser parser = new SpelExpressionParser();
-
-// invokes 'getBytes()'
-Expression exp = parser.parseExpression("'Hello World'.bytes");
-
-byte[] bytes = (byte[]) exp.getValue();
-
- SpEL also supports nested properties using standard 'dot' notation,
- i.e. prop1.prop2.prop3 and the setting of property values
-
- Public fields may also be accessed.
-
- ExpressionParser parser = new SpelExpressionParser();
-
-// invokes 'getBytes().length'
-Expression exp = parser.parseExpression("'Hello World'.bytes.length");
-
-int length = (Integer) exp.getValue();
-
- The String's constructor can be called instead of using a string
- literal.
-
- ExpressionParser parser = new SpelExpressionParser();
-Expression exp = parser.parseExpression("new String('hello world').toUpperCase()");
-String message = exp.getValue(String.class);
-
- Note the use of the generic method public <T> T
- getValue(Class<T> desiredResultType). Using this method
- removes the need to cast the value of the expression to the desired result
- type. An EvaluationException will be thrown if the
- value cannot be cast to the type T or converted using
- the registered type converter.
-
- The more common usage of SpEL is to provide an expression string that
- is evaluated against a specific object instance (called the root object).
- There are two options here and which to choose depends on whether the object
- against which the expression is being evaluated will be changing with each
- call to evaluate the expression. In the following example
- we retrieve the name property from an instance of the
- Inventor class.
-
- // Create and set a calendar
-GregorianCalendar c = new GregorianCalendar();
-c.set(1856, 7, 9);
-
-// The constructor arguments are name, birthday, and nationality.
-Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
-
-ExpressionParser parser = new SpelExpressionParser();
-Expression exp = parser.parseExpression("name");
-
-EvaluationContext context = new StandardEvaluationContext();
-context.setRootObject(tesla);
-
-String name = (String) exp.getValue(context);
-In the last
- line, the value of the string variable 'name' will be set to "Nikola
- Tesla". The class StandardEvaluationContext is where you can specify which
- object the "name" property will be evaluated against. This is the mechanism
- to use if the root object is unlikely to change, it can simply be set once
- in the evaluation context. If the root object is likely to change
- repeatedly, it can be supplied on each call to getValue,
- as this next example shows:
-
- / Create and set a calendar
-GregorianCalendar c = new GregorianCalendar();
-c.set(1856, 7, 9);
-
-// The constructor arguments are name, birthday, and nationality.
-Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
-
-ExpressionParser parser = new SpelExpressionParser();
-Expression exp = parser.parseExpression("name");
-
-String name = (String) exp.getValue(tesla);
- In this case the inventor tesla has been
- supplied directly to getValue and the expression
- evaluation infrastructure creates and manages a default evaluation context
- internally - it did not require one to be supplied.
-
- The StandardEvaluationContext is relatively expensive to construct and
- during repeated usage it builds up cached state that enables subsequent
- expression evaluations to be performed more quickly. For this reason it is
- better to cache and reuse them where possible, rather than construct a new
- one for each expression evaluation.
-
- In some cases it can be desirable to use a configured evaluation context and
- yet still supply a different root object on each call to getValue.
- getValue allows both to be specified on the same call.
- In these situations the root object passed on the call is considered to override
- any (which maybe null) specified on the evaluation context.
-
-
-
- In standalone usage of SpEL there is a need to create the parser,
- parse expressions and perhaps provide evaluation contexts and a root
- context object. However, more common usage
- is to provide only the SpEL expression string as part of a
- configuration file, for example for Spring bean or Spring Web Flow
- definitions. In this case, the parser, evaluation context, root object
- and any predefined variables are all set up implicitly, requiring
- the user to specify nothing other than the expressions.
-
- As a final introductory example, the use of a boolean operator is
- shown using the Inventor object in the previous example.
-
- Expression exp = parser.parseExpression("name == 'Nikola Tesla'");
-boolean result = exp.getValue(context, Boolean.class); // evaluates to true
-
-
- The EvaluationContext interface
-
- The interface EvaluationContext is
- used when evaluating an expression to resolve properties, methods,
- fields, and to help perform type conversion. The out-of-the-box
- implementation, StandardEvaluationContext, uses
- reflection to manipulate the object, caching
- java.lang.reflect's Method,
- Field, and Constructor
- instances for increased performance.
-
- The StandardEvaluationContext is where you
- may specify the root object to evaluate against via the method
- setRootObject or passing the root object into
- the constructor. You can also specify variables and functions that
- will be used in the expression using the methods
- setVariable and
- registerFunction. The use of variables and
- functions are described in the language reference sections Variables and Functions. The
- StandardEvaluationContext is also where you can
- register custom ConstructorResolvers,
- MethodResolvers, and
- PropertyAccessors to extend how SpEL evaluates
- expressions. Please refer to the JavaDoc of these classes for more
- details.
-
-
- Type Conversion
-
- By default SpEL uses the conversion service available in Spring
- core
- (org.springframework.core.convert.ConversionService).
- This conversion service comes with many converters built in for common
- conversions but is also fully extensible so custom conversions between
- types can be added. Additionally it has the key capability that it is
- generics aware. This means that when working with generic types in
- expressions, SpEL will attempt conversions to maintain type
- correctness for any objects it encounters.
-
- What does this mean in practice? Suppose assignment, using
- setValue(), is being used to set a
- List property. The type of the property is actually
- List<Boolean>. SpEL will recognize that the
- elements of the list need to be converted to
- Boolean before being placed in it. A simple
- example:
-
- class Simple {
- public List<Boolean> booleanList = new ArrayList<Boolean>();
-}
-
-Simple simple = new Simple();
-
-simple.booleanList.add(true);
-
-StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple);
-
-// false is passed in here as a string. SpEL and the conversion service will
-// correctly recognize that it needs to be a Boolean and convert it
-parser.parseExpression("booleanList[0]").setValue(simpleContext, "false");
-
-// b will be false
-Boolean b = simple.booleanList.get(0);
-
-
-
-
-
-
- Expression support for defining bean definitions
-
- SpEL expressions can be used with XML or annotation based
- configuration metadata for defining BeanDefinitions. In both cases the
- syntax to define the expression is of the form #{ <expression
- string> }.
-
-
- XML based configuration
-
- A property or constructor-arg value can be set using expressions
- as shown below
-
- <bean id="numberGuess" class="org.spring.samples.NumberGuess">
- <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
-
- <!-- other properties -->
-</bean>
-
- The variable 'systemProperties' is predefined, so you can use it
- in your expressions as shown below. Note that you do not have to prefix
- the predefined variable with the '#' symbol in this context.
-
- <bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
- <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
-
- <!-- other properties -->
-</bean>
-
- You can also refer to other bean properties by name, for
- example.
-
- <bean id="numberGuess" class="org.spring.samples.NumberGuess">
- <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
-
- <!-- other properties -->
-</bean>
-
-
-<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
- <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
-
- <!-- other properties -->
-</bean>
-
-
-
- Annotation-based configuration
-
- The @Value annotation can be placed on fields,
- methods and method/constructor parameters to specify a default
- value.
-
- Here is an example to set the default value of a field
- variable.
-
- public static class FieldValueTestBean
-
- @Value("#{ systemProperties['user.region'] }")
- private String defaultLocale;
-
- public void setDefaultLocale(String defaultLocale)
- {
- this.defaultLocale = defaultLocale;
- }
-
- public String getDefaultLocale()
- {
- return this.defaultLocale;
- }
-
-}
-
-
-
- The equivalent but on a property setter method is shown
- below.
-
- public static class PropertyValueTestBean
-
- private String defaultLocale;
-
- @Value("#{ systemProperties['user.region'] }")
- public void setDefaultLocale(String defaultLocale)
- {
- this.defaultLocale = defaultLocale;
- }
-
- public String getDefaultLocale()
- {
- return this.defaultLocale;
- }
-
-}
-
- Autowired methods and constructors can also use the
- @Value annotation.
-
- public class SimpleMovieLister {
-
- private MovieFinder movieFinder;
- private String defaultLocale;
-
- @Autowired
- public void configure(MovieFinder movieFinder,
- @Value("#{ systemProperties['user.region'] }"} String defaultLocale) {
- this.movieFinder = movieFinder;
- this.defaultLocale = defaultLocale;
- }
-
- // ...
-}
-
- public class MovieRecommender {
-
- private String defaultLocale;
-
- private CustomerPreferenceDao customerPreferenceDao;
-
- @Autowired
- public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
- @Value("#{systemProperties['user.country']}"} String defaultLocale) {
- this.customerPreferenceDao = customerPreferenceDao;
- this.defaultLocale = defaultLocale;
- }
-
- // ...
-}
-
-
-
-
- Language Reference
-
-
- Literal expressions
-
- The types of literal expressions supported are strings, dates,
- numeric values (int, real, and hex), boolean and null. Strings are
- delimited by single quotes. To put a single quote itself in a string use
- the backslash character. The following listing shows simple usage of
- literals. Typically they would not be used in isolation like this, but
- as part of a more complex expression, for example using a literal on one
- side of a logical comparison operator.
-
- ExpressionParser parser = new SpelExpressionParser();
-
-// evals to "Hello World"
-String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
-
-double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
-
-// evals to 2147483647
-int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
-
-boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
-
-Object nullValue = parser.parseExpression("null").getValue();
-
-
- Numbers support the use of the negative sign, exponential
- notation, and decimal points. By default real numbers are parsed using
- Double.parseDouble().
-
-
-
- Properties, Arrays, Lists, Maps, Indexers
-
- Navigating with property references is easy, just use a period to
- indicate a nested property value. The instances of Inventor class, pupin
- and tesla, were populated with data listed in the section Classes used in the
- examples. To navigate "down" and get Tesla's year of birth and
- Pupin's city of birth the following expressions are used.
-
- // evals to 1856
-int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);
-
-
-String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
-
- Case insensitivity is allowed for the first letter of property
- names. The contents of arrays and lists are obtained using square
- bracket notation.
-
- ExpressionParser parser = new SpelExpressionParser();
-
-// Inventions Array
-StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);
-
-// evaluates to "Induction motor"
-String invention = parser.parseExpression("inventions[3]").getValue(teslaContext,
- String.class);
-
-
-// Members List
-StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);
-
-// evaluates to "Nikola Tesla"
-String name = parser.parseExpression("Members[0].Name").getValue(societyContext, String.class);
-
-// List and Array navigation
-// evaluates to "Wireless communication"
-String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(societyContext,
- String.class);
-
-
- The contents of maps are obtained by specifying the literal key
- value within the brackets. In this case, because keys for the Officers
- map are strings, we can specify string literals.
-
- // Officer's Dictionary
-
-Inventor pupin = parser.parseExpression("Officers['president']").getValue(societyContext,
- Inventor.class);
-
-// evaluates to "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");
-
-
-
-
-
- Methods
-
- Methods are invoked using typical Java programming syntax. You may
- also invoke methods on literals. Varargs are also supported.
-
- // string literal, evaluates to "bc"
-String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);
-
-// evaluates to true
-boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(societyContext,
- Boolean.class);
-
-
-
- Operators
-
-
- Relational operators
-
- The relational operators; equal, not equal, less than, less than
- or equal, greater than, and greater than or equal are supported using
- standard operator notation.
-
- // evaluates to true
-boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
-
-// evaluates to false
-boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
-
-// evaluates to true
-boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
- In addition to standard relational operators SpEL supports the
- 'instanceof' and regular expression based 'matches' operator.
-
- // evaluates to false
-boolean falseValue = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class);
-
-// evaluates to true
-boolean trueValue =
- parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
-
-//evaluates to false
-boolean falseValue =
- parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
-
-
- Each symbolic operator can also be specified as a purely alphabetic equivalent. This avoids
- problems where the symbols used have special meaning for the document type in which
- the expression is embedded (eg. an XML document). The textual equivalents are shown
- here: lt ('<'), gt ('>'), le ('<='), ge ('>='),
- eq ('=='), ne ('!='), div ('/'), mod ('%'), not ('!').
- These are case insensitive.
-
-
-
- Logical operators
-
- The logical operators that are supported are and, or, and not.
- Their use is demonstrated below.
-
- // -- AND --
-
-// evaluates to false
-boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
-
-// evaluates to true
-String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
-boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
-
-// -- OR --
-
-// evaluates to true
-boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
-
-// evaluates to true
-String expression = "isMember('Nikola Tesla') or isMember('Albert Einstien')";
-boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
-
-// -- NOT --
-
-// evaluates to 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);
-
-
-
- Mathematical operators
-
- The addition operator can be used on numbers, strings and dates.
- Subtraction can be used on numbers and dates. Multiplication and
- division can be used only on numbers. Other mathematical operators
- supported are modulus (%) and exponential power (^). Standard operator
- precedence is enforced. These operators are demonstrated below.
-
- // 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
-
-// Modulus
-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
-
-
-
-
-
- Assignment
-
- Setting of a property is done by using the assignment operator.
- This would typically be done within a call to
- setValue but can also be done inside a call to
- getValue.
-
- Inventor inventor = new Inventor();
-StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);
-
-parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2");
-
-// alternatively
-
-String aleks = parser.parseExpression("Name = 'Alexandar Seovic'").getValue(inventorContext,
- String.class);
-
-
-
-
-
-
- Types
-
- The special 'T' operator can be used to specify an instance of
- java.lang.Class (the 'type'). Static methods are invoked using this
- operator as well. The StandardEvaluationContext
- uses a TypeLocator to find types and the
- StandardTypeLocator (which can be replaced) is
- built with an understanding of the java.lang package. This means T()
- references to types within java.lang do not need to be fully qualified,
- but all other type references must be.
-
- 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);
-
-
-
-
- Constructors
-
- Constructors can be invoked using the new operator. The fully
- qualified class name should be used for all but the primitive type and
- String (where int, float, etc, can be used).
-
- Inventor einstein =
- p.parseExpression("new org.spring.samples.spel.inventor.Inventor('Albert Einstein',
- 'German')")
- .getValue(Inventor.class);
-
-//create new inventor instance within add method of List
-p.parseExpression("Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein',
- 'German'))")
- .getValue(societyContext);
-
-
-
-
- Variables
-
- Variables can be referenced in the expression using the syntax
- #variableName. Variables are set using the method setVariable on the
- StandardEvaluationContext.
-
- Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
-StandardEvaluationContext context = new StandardEvaluationContext(tesla);
-context.setVariable("newName", "Mike Tesla");
-
-parser.parseExpression("Name = #newName").getValue(context);
-
-System.out.println(tesla.getName()) // "Mike Tesla"
-
-
- The #this and #root variables
-
- The variable #this is always defined and refers to the current
- evaluation object (against which unqualified references are resolved).
- The variable #root is always defined and refers to the root
- context object. Although #this may vary as components of an expression
- are evaluated, #root always refers to the root.
-
- // create an array of integers
-List<Integer> primes = new ArrayList<Integer>();
-primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
-
-// create parser and set variable 'primes' as the array of integers
-ExpressionParser parser = new SpelExpressionParser();
-StandardEvaluationContext context = new StandardEvaluationContext();
-context.setVariable("primes",primes);
-
-// all prime numbers > 10 from the list (using selection ?{...})
-// evaluates to [11, 13, 17]
-List<Integer> primesGreaterThanTen =
- (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context);
-
-
-
-
-
-
-
-
- Functions
-
- You can extend SpEL by registering user defined functions that can
- be called within the expression string. The function is registered with
- the StandardEvaluationContext using the
- method.
-
- public void registerFunction(String name, Method m)
-
- A reference to a Java Method provides the implementation of the
- function. For example, a utility method to reverse a string is shown
- below.
-
- public abstract class StringUtils {
-
- public static String reverseString(String input) {
- StringBuilder backwards = new StringBuilder();
- for (int i = 0; i < input.length(); i++)
- backwards.append(input.charAt(input.length() - 1 - i));
- }
- return backwards.toString();
- }
-}
-
- This method is then registered with the evaluation context and can
- be used within an expression string.
-
- ExpressionParser parser = new SpelExpressionParser();
-StandardEvaluationContext context = new StandardEvaluationContext();
-
-context.registerFunction("reverseString",
- StringUtils.class.getDeclaredMethod("reverseString",
- new Class[] { String.class }));
-
-String helloWorldReversed =
- parser.parseExpression("#reverseString('hello')").getValue(context, String.class);
-
-
-
- Ternary Operator (If-Then-Else)
-
- You can use the ternary operator for performing if-then-else
- conditional logic inside the expression. A minimal example is:
-
- String falseString =
- parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class);
-
- In this case, the boolean false results in returning the string
- value 'falseExp'. A more realistic example is shown below.
-
- 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"
-
- Also see the next section on the Elvis operator for an even
- shorter syntax for the ternary operator.
-
-
-
- The Elvis Operator
-
- The Elvis operator is a shortening of the ternary operator syntax
- and is used in the Groovy
- language. With the ternary operator syntax you usually have to repeat a
- variable twice, for example:
-
- String name = "Elvis Presley";
-String displayName = name != null ? name : "Unknown";
-
- Instead you can use the Elvis operator, named for the resemblance
- to Elvis' hair style.
-
- ExpressionParser parser = new SpelExpressionParser();
-
-String name = parser.parseExpression("null?:'Unknown'").getValue(String.class);
-
-System.out.println(name); // 'Unknown'
-
-
-
- Here is a more complex example.
-
- ExpressionParser parser = new SpelExpressionParser();
-
-Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
-StandardEvaluationContext context = new StandardEvaluationContext(tesla);
-
-String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
-
-System.out.println(name); // Mike Tesla
-
-tesla.setName(null);
-
-name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
-
-System.out.println(name); // Elvis Presley
-
-
-
- Safe Navigation operator
-
- The Safe Navigation operator is used to avoid a
- NullPointerException and comes from the Groovy
- language. Typically when you have a reference to an object you might
- need to verify that it is not null before accessing methods or
- properties of the object. To avoid this, the safe navigation operator
- will simply return null instead of throwing an exception.
-
- ExpressionParser parser = new SpelExpressionParser();
-
-Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
-tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
-
-StandardEvaluationContext context = new StandardEvaluationContext(tesla);
-
-String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
-System.out.println(city); // Smiljan
-
-tesla.setPlaceOfBirth(null);
-
-city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
-
-System.out.println(city); // null - does not throw NullPointerException!!!
-
-
-
- Collection Selection
-
- Selection is a powerful expression language feature that allows you
- to transform some source collection into another by selecting from its
- entries.
-
- Selection uses the syntax
- ?[selectionExpression]. This will filter the
- collection and return a new collection containing a subset of the
- original elements. For example, selection would allow us to easily get a
- list of Serbian inventors:
-
- List<Inventor> list = (List<Inventor>)
- parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext);
-
- Selection is possible upon both lists and maps. In the former case
- the selection criteria is evaluated against each individual list element
- whilst against a map the selection criteria is evaluated against each
- map entry (objects of the Java type Map.Entry). Map
- entries have their key and value accessible as properties for use in the
- selection.
-
- This expression will return a new map consisting of those elements
- of the original map where the entry value is less than 27.
-
- Map newMap = parser.parseExpression("map.?[value<27]").getValue();
-
- In addition to returning all the selected elements, it is possible
- to retrieve just the first or the last value. To obtain the first entry
- matching the selection the syntax is ^[...] whilst to
- obtain the last matching selection the syntax is
- $[...].
-
-
-
- Collection Projection
-
- Projection allows a collection to drive the evaluation of a
- sub-expression and the result is a new collection. The syntax for
- projection is ![projectionExpression]. Most easily
- understood by example, suppose we have a list of inventors but want the
- list of cities where they were born. Effectively we want to evaluate
- 'placeOfBirth.city' for every entry in the inventor list. Using
- projection:
-
- // returns [ 'Smiljan', 'Idvor' ]
-List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
-
- A map can also be used to drive projection and in this case the
- projection expression is evaluated against each entry in the map
- (represented as a Java Map.Entry). The result of a
- projection across a map is a list consisting of the evaluation of the
- projection expression against each map entry.
-
-
-
- Expression templating
-
- Expression templates allow a mixing of literal text with one or
- more evaluation blocks. Each evaluation block is delimited with prefix
- and suffix characters that you can define, a common choice is to use
- ${} as the delimiters. For example,
-
- String randomPhrase =
- parser.parseExpression("random number is ${T(java.lang.Math).random()}",
- new TemplatedParserContext()).getValue(String.class);
-
-// evaluates to "random number is 0.7038186818312008"
-
- The string is evaluated by concatenating the literal text 'random
- number is ' with the result of evaluating the expression inside the ${}
- delimiter, in this case the result of calling that random() method. The
- second argument to the method parseExpression() is of
- the type ParserContext. The
- ParserContext interface is used to
- influence how the expression is parsed in order to support the
- expression templating functionality. The definition of
- TemplatedParserContext is shown below.
-
- public class TemplatedParserContext implements ParserContext {
-
- public String getExpressionPrefix() {
- return "${";
- }
-
- public String getExpressionSuffix() {
- return "}";
- }
-
- public boolean isTemplate() {
- return true;
- }
-}
-
-
-
-
- Classes used in the examples
-
- Inventor.java
-
- package org.spring.samples.spel.inventor;
-
-import java.util.Date;
-import java.util.GregorianCalendar;
-
-public class Inventor {
-
- private String name;
- private String nationality;
- private String[] inventions;
- private Date birthdate;
- private PlaceOfBirth placeOfBirth;
-
-
- public Inventor(String name, String nationality)
- {
- GregorianCalendar c= new GregorianCalendar();
- this.name = name;
- this.nationality = nationality;
- this.birthdate = c.getTime();
- }
- public Inventor(String name, Date birthdate, String nationality) {
- this.name = name;
- this.nationality = nationality;
- this.birthdate = birthdate;
- }
-
- public Inventor() {
- }
-
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getNationality() {
- return nationality;
- }
- public void setNationality(String nationality) {
- this.nationality = nationality;
- }
- public Date getBirthdate() {
- return birthdate;
- }
- public void setBirthdate(Date birthdate) {
- this.birthdate = birthdate;
- }
- public PlaceOfBirth getPlaceOfBirth() {
- return placeOfBirth;
- }
- public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
- this.placeOfBirth = placeOfBirth;
- }
- public void setInventions(String[] inventions) {
- this.inventions = inventions;
- }
- public String[] getInventions() {
- return inventions;
- }
-}
-
-
- PlaceOfBirth.java
-
- package org.spring.samples.spel.inventor;
-
-public class PlaceOfBirth {
-
- private String city;
- private String country;
-
- public PlaceOfBirth(String city) {
- this.city=city;
- }
- public PlaceOfBirth(String city, String country)
- {
- this(city);
- this.country = country;
- }
-
-
- public String getCity() {
- return city;
- }
- public void setCity(String s) {
- this.city = s;
- }
- public String getCountry() {
- return country;
- }
- public void setCountry(String country) {
- this.country = country;
- }
-
-
-
-}
-
-
- Society.java
-
- package org.spring.samples.spel.inventor;
-
-import java.util.*;
-
-public class Society {
-
- private String name;
-
- public static String Advisors = "advisors";
- public static String President = "president";
-
- private List<Inventor> members = new ArrayList<Inventor>();
- private Map officers = new HashMap();
-
- public List getMembers() {
- return members;
- }
-
- public Map getOfficers() {
- return officers;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public boolean isMember(String name)
- {
- boolean found = false;
- for (Inventor inventor : members) {
- if (inventor.getName().equals(name))
- {
- found = true;
- break;
- }
- }
- return found;
- }
-
-
-}
-
-
-
+
+
+
+ Spring Expression Language (SpEL)
+
+
+ Introduction
+
+ The Spring Expression Language (SpEL for short) is a powerful
+ expression language that supports querying and manipulating an object
+ graph at runtime. The language syntax is similar to Unified EL but offers
+ additional features, most notably method invocation and basic string
+ templating functionality.
+
+ While there are several other Java expression languages available,
+ OGNL, MVEL, and JBoss EL, to name a few, the Spring Expression Language
+ was created to provide the Spring community with a single well supported
+ expression language that can be used across all the products in the Spring
+ portfolio. Its language features are driven by the requirements of the
+ projects in the Spring portfolio, including tooling requirements for code
+ completion support within the eclipse based SpringSource Tool Suite. That
+ said, SpEL is based on a technology agnostic API allowing other
+ expression language implementations to be integrated should the need
+ arise.
+
+ While SpEL serves as the foundation for expression evaluation within
+ the Spring portfolio, it is not directly tied to Spring and can be used
+ independently. In order to be self contained, many of the examples in this
+ chapter use SpEL as if it were an independent expression language. This
+ requires creating a few bootstrapping infrastructure classes such as the
+ parser. Most Spring users will not need to deal with this infrastructure
+ and will instead only author expression strings for evaluation. An example
+ of this typical use is the integration of SpEL into creating XML or
+ annotated based bean definitions as shown in the section Expression support for defining bean
+ definitions.
+
+ This chapter covers the features of the expression language, its
+ API, and its language syntax. In several places an Inventor and Inventor's
+ Society class are used as the target objects for expression evaluation.
+ These class declarations and the data used to populate them are listed at
+ the end of the chapter.
+
+
+
+ Feature Overview
+
+ The expression language supports the following functionality
+
+
+
+ Literal expressions
+
+
+
+ Boolean and relational operators
+
+
+
+ Regular expressions
+
+
+
+ Class expressions
+
+
+
+ Accessing properties, arrays, lists, maps
+
+
+
+ Method invocation
+
+
+
+ Relational operators
+
+
+
+ Assignment
+
+
+
+ Calling constructors
+
+
+
+ Ternary operator
+
+
+
+ Variables
+
+
+
+ User defined functions
+
+
+
+ Collection projection
+
+
+
+ Collection selection
+
+
+
+ Templated expressions
+
+
+
+
+
+ Expression Evaluation using Spring's Expression Interface
+
+ This section introduces the simple use of SpEL interfaces and its
+ expression language. The complete language reference can be found in the
+ section Language
+ Reference.
+
+ The following code introduces the SpEL API to evaluate the literal
+ string expression 'Hello World'.
+
+ ExpressionParser parser = new SpelExpressionParser();
+Expression exp = parser.parseExpression("'Hello World'");
+String message = (String) exp.getValue();The value of the
+ message variable is simply 'Hello World'.
+
+ The SpEL classes and interfaces you are most likely to use are
+ located in the packages org.springframework.expression
+ and its sub packages and spel.support.
+
+ The interface ExpressionParser is
+ responsible for parsing an expression string. In this example the
+ expression string is a string literal denoted by the surrounding single
+ quotes. The interface Expression is
+ responsible for evaluating the previously defined expression string. There
+ are two exceptions that can be thrown,
+ ParseException and
+ EvaluationException when calling
+ 'parser.parseExpression' and
+ 'exp.getValue' respectively.
+
+ SpEL supports a wide range of features, such as calling methods,
+ accessing properties, and calling constructors.
+
+ As an example of method invocation, we call the 'concat' method on
+ the string literal.
+
+ ExpressionParser parser = new SpelExpressionParser();
+Expression exp = parser.parseExpression("'Hello World'.concat('!')");
+String message = (String) exp.getValue();
+
+ The value of message is now 'Hello World!'.
+
+ As an example of calling a JavaBean property, the String property
+ 'Bytes' can be called as shown below.
+
+ ExpressionParser parser = new SpelExpressionParser();
+
+// invokes 'getBytes()'
+Expression exp = parser.parseExpression("'Hello World'.bytes");
+
+byte[] bytes = (byte[]) exp.getValue();
+
+ SpEL also supports nested properties using standard 'dot' notation,
+ i.e. prop1.prop2.prop3 and the setting of property values
+
+ Public fields may also be accessed.
+
+ ExpressionParser parser = new SpelExpressionParser();
+
+// invokes 'getBytes().length'
+Expression exp = parser.parseExpression("'Hello World'.bytes.length");
+
+int length = (Integer) exp.getValue();
+
+ The String's constructor can be called instead of using a string
+ literal.
+
+ ExpressionParser parser = new SpelExpressionParser();
+Expression exp = parser.parseExpression("new String('hello world').toUpperCase()");
+String message = exp.getValue(String.class);
+
+ Note the use of the generic method public <T> T
+ getValue(Class<T> desiredResultType). Using this method
+ removes the need to cast the value of the expression to the desired result
+ type. An EvaluationException will be thrown if the
+ value cannot be cast to the type T or converted using
+ the registered type converter.
+
+ The more common usage of SpEL is to provide an expression string that
+ is evaluated against a specific object instance (called the root object).
+ There are two options here and which to choose depends on whether the object
+ against which the expression is being evaluated will be changing with each
+ call to evaluate the expression. In the following example
+ we retrieve the name property from an instance of the
+ Inventor class.
+
+ // Create and set a calendar
+GregorianCalendar c = new GregorianCalendar();
+c.set(1856, 7, 9);
+
+// The constructor arguments are name, birthday, and nationality.
+Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
+
+ExpressionParser parser = new SpelExpressionParser();
+Expression exp = parser.parseExpression("name");
+EvaluationContext context = new StandardEvaluationContext(tesla);
+
+String name = (String) exp.getValue(context);
+In the last
+ line, the value of the string variable 'name' will be set to "Nikola
+ Tesla". The class StandardEvaluationContext is where you can specify which
+ object the "name" property will be evaluated against. This is the mechanism
+ to use if the root object is unlikely to change, it can simply be set once
+ in the evaluation context. If the root object is likely to change
+ repeatedly, it can be supplied on each call to getValue,
+ as this next example shows:
+
+ / Create and set a calendar
+GregorianCalendar c = new GregorianCalendar();
+c.set(1856, 7, 9);
+
+// The constructor arguments are name, birthday, and nationality.
+Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
+
+ExpressionParser parser = new SpelExpressionParser();
+Expression exp = parser.parseExpression("name");
+
+String name = (String) exp.getValue(tesla);
+ In this case the inventor tesla has been
+ supplied directly to getValue and the expression
+ evaluation infrastructure creates and manages a default evaluation context
+ internally - it did not require one to be supplied.
+
+ The StandardEvaluationContext is relatively expensive to construct and
+ during repeated usage it builds up cached state that enables subsequent
+ expression evaluations to be performed more quickly. For this reason it is
+ better to cache and reuse them where possible, rather than construct a new
+ one for each expression evaluation.
+
+ In some cases it can be desirable to use a configured evaluation context and
+ yet still supply a different root object on each call to getValue.
+ getValue allows both to be specified on the same call.
+ In these situations the root object passed on the call is considered to override
+ any (which maybe null) specified on the evaluation context.
+
+
+
+ In standalone usage of SpEL there is a need to create the parser,
+ parse expressions and perhaps provide evaluation contexts and a root
+ context object. However, more common usage
+ is to provide only the SpEL expression string as part of a
+ configuration file, for example for Spring bean or Spring Web Flow
+ definitions. In this case, the parser, evaluation context, root object
+ and any predefined variables are all set up implicitly, requiring
+ the user to specify nothing other than the expressions.
+
+ As a final introductory example, the use of a boolean operator is
+ shown using the Inventor object in the previous example.
+
+ Expression exp = parser.parseExpression("name == 'Nikola Tesla'");
+boolean result = exp.getValue(context, Boolean.class); // evaluates to true
+
+
+ The EvaluationContext interface
+
+ The interface EvaluationContext is
+ used when evaluating an expression to resolve properties, methods,
+ fields, and to help perform type conversion. The out-of-the-box
+ implementation, StandardEvaluationContext, uses
+ reflection to manipulate the object, caching
+ java.lang.reflect's Method,
+ Field, and Constructor
+ instances for increased performance.
+
+ The StandardEvaluationContext is where you
+ may specify the root object to evaluate against via the method
+ setRootObject or passing the root object into
+ the constructor. You can also specify variables and functions that
+ will be used in the expression using the methods
+ setVariable and
+ registerFunction. The use of variables and
+ functions are described in the language reference sections Variables and Functions. The
+ StandardEvaluationContext is also where you can
+ register custom ConstructorResolvers,
+ MethodResolvers, and
+ PropertyAccessors to extend how SpEL evaluates
+ expressions. Please refer to the JavaDoc of these classes for more
+ details.
+
+
+ Type Conversion
+
+ By default SpEL uses the conversion service available in Spring
+ core
+ (org.springframework.core.convert.ConversionService).
+ This conversion service comes with many converters built in for common
+ conversions but is also fully extensible so custom conversions between
+ types can be added. Additionally it has the key capability that it is
+ generics aware. This means that when working with generic types in
+ expressions, SpEL will attempt conversions to maintain type
+ correctness for any objects it encounters.
+
+ What does this mean in practice? Suppose assignment, using
+ setValue(), is being used to set a
+ List property. The type of the property is actually
+ List<Boolean>. SpEL will recognize that the
+ elements of the list need to be converted to
+ Boolean before being placed in it. A simple
+ example:
+
+ class Simple {
+ public List<Boolean> booleanList = new ArrayList<Boolean>();
+}
+
+Simple simple = new Simple();
+
+simple.booleanList.add(true);
+
+StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple);
+
+// false is passed in here as a string. SpEL and the conversion service will
+// correctly recognize that it needs to be a Boolean and convert it
+parser.parseExpression("booleanList[0]").setValue(simpleContext, "false");
+
+// b will be false
+Boolean b = simple.booleanList.get(0);
+
+
+
+
+
+
+ Expression support for defining bean definitions
+
+ SpEL expressions can be used with XML or annotation based
+ configuration metadata for defining BeanDefinitions. In both cases the
+ syntax to define the expression is of the form #{ <expression
+ string> }.
+
+
+ XML based configuration
+
+ A property or constructor-arg value can be set using expressions
+ as shown below
+
+ <bean id="numberGuess" class="org.spring.samples.NumberGuess">
+ <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
+
+ <!-- other properties -->
+</bean>
+
+ The variable 'systemProperties' is predefined, so you can use it
+ in your expressions as shown below. Note that you do not have to prefix
+ the predefined variable with the '#' symbol in this context.
+
+ <bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
+ <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
+
+ <!-- other properties -->
+</bean>
+
+ You can also refer to other bean properties by name, for
+ example.
+
+ <bean id="numberGuess" class="org.spring.samples.NumberGuess">
+ <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
+
+ <!-- other properties -->
+</bean>
+
+
+<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
+ <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
+
+ <!-- other properties -->
+</bean>
+
+
+
+ Annotation-based configuration
+
+ The @Value annotation can be placed on fields,
+ methods and method/constructor parameters to specify a default
+ value.
+
+ Here is an example to set the default value of a field
+ variable.
+
+ public static class FieldValueTestBean
+
+ @Value("#{ systemProperties['user.region'] }")
+ private String defaultLocale;
+
+ public void setDefaultLocale(String defaultLocale)
+ {
+ this.defaultLocale = defaultLocale;
+ }
+
+ public String getDefaultLocale()
+ {
+ return this.defaultLocale;
+ }
+
+}
+
+
+
+ The equivalent but on a property setter method is shown
+ below.
+
+ public static class PropertyValueTestBean
+
+ private String defaultLocale;
+
+ @Value("#{ systemProperties['user.region'] }")
+ public void setDefaultLocale(String defaultLocale)
+ {
+ this.defaultLocale = defaultLocale;
+ }
+
+ public String getDefaultLocale()
+ {
+ return this.defaultLocale;
+ }
+
+}
+
+ Autowired methods and constructors can also use the
+ @Value annotation.
+
+ public class SimpleMovieLister {
+
+ private MovieFinder movieFinder;
+ private String defaultLocale;
+
+ @Autowired
+ public void configure(MovieFinder movieFinder,
+ @Value("#{ systemProperties['user.region'] }"} String defaultLocale) {
+ this.movieFinder = movieFinder;
+ this.defaultLocale = defaultLocale;
+ }
+
+ // ...
+}
+
+ public class MovieRecommender {
+
+ private String defaultLocale;
+
+ private CustomerPreferenceDao customerPreferenceDao;
+
+ @Autowired
+ public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
+ @Value("#{systemProperties['user.country']}"} String defaultLocale) {
+ this.customerPreferenceDao = customerPreferenceDao;
+ this.defaultLocale = defaultLocale;
+ }
+
+ // ...
+}
+
+
+
+
+ Language Reference
+
+
+ Literal expressions
+
+ The types of literal expressions supported are strings, dates,
+ numeric values (int, real, and hex), boolean and null. Strings are
+ delimited by single quotes. To put a single quote itself in a string use
+ the backslash character. The following listing shows simple usage of
+ literals. Typically they would not be used in isolation like this, but
+ as part of a more complex expression, for example using a literal on one
+ side of a logical comparison operator.
+
+ ExpressionParser parser = new SpelExpressionParser();
+
+// evals to "Hello World"
+String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
+
+double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
+
+// evals to 2147483647
+int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
+
+boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
+
+Object nullValue = parser.parseExpression("null").getValue();
+
+
+ Numbers support the use of the negative sign, exponential
+ notation, and decimal points. By default real numbers are parsed using
+ Double.parseDouble().
+
+
+
+ Properties, Arrays, Lists, Maps, Indexers
+
+ Navigating with property references is easy, just use a period to
+ indicate a nested property value. The instances of Inventor class, pupin
+ and tesla, were populated with data listed in the section Classes used in the
+ examples. To navigate "down" and get Tesla's year of birth and
+ Pupin's city of birth the following expressions are used.
+
+ // evals to 1856
+int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);
+
+
+String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
+
+ Case insensitivity is allowed for the first letter of property
+ names. The contents of arrays and lists are obtained using square
+ bracket notation.
+
+ ExpressionParser parser = new SpelExpressionParser();
+
+// Inventions Array
+StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);
+
+// evaluates to "Induction motor"
+String invention = parser.parseExpression("inventions[3]").getValue(teslaContext,
+ String.class);
+
+
+// Members List
+StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);
+
+// evaluates to "Nikola Tesla"
+String name = parser.parseExpression("Members[0].Name").getValue(societyContext, String.class);
+
+// List and Array navigation
+// evaluates to "Wireless communication"
+String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(societyContext,
+ String.class);
+
+
+ The contents of maps are obtained by specifying the literal key
+ value within the brackets. In this case, because keys for the Officers
+ map are strings, we can specify string literals.
+
+ // Officer's Dictionary
+
+Inventor pupin = parser.parseExpression("Officers['president']").getValue(societyContext,
+ Inventor.class);
+
+// evaluates to "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");
+
+
+
+
+
+ Methods
+
+ Methods are invoked using typical Java programming syntax. You may
+ also invoke methods on literals. Varargs are also supported.
+
+ // string literal, evaluates to "bc"
+String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);
+
+// evaluates to true
+boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(societyContext,
+ Boolean.class);
+
+
+
+ Operators
+
+
+ Relational operators
+
+ The relational operators; equal, not equal, less than, less than
+ or equal, greater than, and greater than or equal are supported using
+ standard operator notation.
+
+ // evaluates to true
+boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
+
+// evaluates to false
+boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
+
+// evaluates to true
+boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
+ In addition to standard relational operators SpEL supports the
+ 'instanceof' and regular expression based 'matches' operator.
+
+ // evaluates to false
+boolean falseValue = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class);
+
+// evaluates to true
+boolean trueValue =
+ parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
+
+//evaluates to false
+boolean falseValue =
+ parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
+
+
+ Each symbolic operator can also be specified as a purely alphabetic equivalent. This avoids
+ problems where the symbols used have special meaning for the document type in which
+ the expression is embedded (eg. an XML document). The textual equivalents are shown
+ here: lt ('<'), gt ('>'), le ('<='), ge ('>='),
+ eq ('=='), ne ('!='), div ('/'), mod ('%'), not ('!').
+ These are case insensitive.
+
+
+
+ Logical operators
+
+ The logical operators that are supported are and, or, and not.
+ Their use is demonstrated below.
+
+ // -- AND --
+
+// evaluates to false
+boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
+
+// evaluates to true
+String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
+boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
+
+// -- OR --
+
+// evaluates to true
+boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
+
+// evaluates to true
+String expression = "isMember('Nikola Tesla') or isMember('Albert Einstien')";
+boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
+
+// -- NOT --
+
+// evaluates to 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);
+
+
+
+ Mathematical operators
+
+ The addition operator can be used on numbers, strings and dates.
+ Subtraction can be used on numbers and dates. Multiplication and
+ division can be used only on numbers. Other mathematical operators
+ supported are modulus (%) and exponential power (^). Standard operator
+ precedence is enforced. These operators are demonstrated below.
+
+ // 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
+
+// Modulus
+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
+
+
+
+
+
+ Assignment
+
+ Setting of a property is done by using the assignment operator.
+ This would typically be done within a call to
+ setValue but can also be done inside a call to
+ getValue.
+
+ Inventor inventor = new Inventor();
+StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);
+
+parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2");
+
+// alternatively
+
+String aleks = parser.parseExpression("Name = 'Alexandar Seovic'").getValue(inventorContext,
+ String.class);
+
+
+
+
+
+
+ Types
+
+ The special 'T' operator can be used to specify an instance of
+ java.lang.Class (the 'type'). Static methods are invoked using this
+ operator as well. The StandardEvaluationContext
+ uses a TypeLocator to find types and the
+ StandardTypeLocator (which can be replaced) is
+ built with an understanding of the java.lang package. This means T()
+ references to types within java.lang do not need to be fully qualified,
+ but all other type references must be.
+
+ 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);
+
+
+
+
+ Constructors
+
+ Constructors can be invoked using the new operator. The fully
+ qualified class name should be used for all but the primitive type and
+ String (where int, float, etc, can be used).
+
+ Inventor einstein =
+ p.parseExpression("new org.spring.samples.spel.inventor.Inventor('Albert Einstein',
+ 'German')")
+ .getValue(Inventor.class);
+
+//create new inventor instance within add method of List
+p.parseExpression("Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein',
+ 'German'))")
+ .getValue(societyContext);
+
+
+
+
+ Variables
+
+ Variables can be referenced in the expression using the syntax
+ #variableName. Variables are set using the method setVariable on the
+ StandardEvaluationContext.
+
+ Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
+StandardEvaluationContext context = new StandardEvaluationContext(tesla);
+context.setVariable("newName", "Mike Tesla");
+
+parser.parseExpression("Name = #newName").getValue(context);
+
+System.out.println(tesla.getName()) // "Mike Tesla"
+
+
+ The #this and #root variables
+
+ The variable #this is always defined and refers to the current
+ evaluation object (against which unqualified references are resolved).
+ The variable #root is always defined and refers to the root
+ context object. Although #this may vary as components of an expression
+ are evaluated, #root always refers to the root.
+
+ // create an array of integers
+List<Integer> primes = new ArrayList<Integer>();
+primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
+
+// create parser and set variable 'primes' as the array of integers
+ExpressionParser parser = new SpelExpressionParser();
+StandardEvaluationContext context = new StandardEvaluationContext();
+context.setVariable("primes",primes);
+
+// all prime numbers > 10 from the list (using selection ?{...})
+// evaluates to [11, 13, 17]
+List<Integer> primesGreaterThanTen =
+ (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context);
+
+
+
+
+
+
+
+
+ Functions
+
+ You can extend SpEL by registering user defined functions that can
+ be called within the expression string. The function is registered with
+ the StandardEvaluationContext using the
+ method.
+
+ public void registerFunction(String name, Method m)
+
+ A reference to a Java Method provides the implementation of the
+ function. For example, a utility method to reverse a string is shown
+ below.
+
+ public abstract class StringUtils {
+
+ public static String reverseString(String input) {
+ StringBuilder backwards = new StringBuilder();
+ for (int i = 0; i < input.length(); i++)
+ backwards.append(input.charAt(input.length() - 1 - i));
+ }
+ return backwards.toString();
+ }
+}
+
+ This method is then registered with the evaluation context and can
+ be used within an expression string.
+
+ ExpressionParser parser = new SpelExpressionParser();
+StandardEvaluationContext context = new StandardEvaluationContext();
+
+context.registerFunction("reverseString",
+ StringUtils.class.getDeclaredMethod("reverseString",
+ new Class[] { String.class }));
+
+String helloWorldReversed =
+ parser.parseExpression("#reverseString('hello')").getValue(context, String.class);
+
+
+
+ Ternary Operator (If-Then-Else)
+
+ You can use the ternary operator for performing if-then-else
+ conditional logic inside the expression. A minimal example is:
+
+ String falseString =
+ parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class);
+
+ In this case, the boolean false results in returning the string
+ value 'falseExp'. A more realistic example is shown below.
+
+ 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"
+
+ Also see the next section on the Elvis operator for an even
+ shorter syntax for the ternary operator.
+
+
+
+ The Elvis Operator
+
+ The Elvis operator is a shortening of the ternary operator syntax
+ and is used in the Groovy
+ language. With the ternary operator syntax you usually have to repeat a
+ variable twice, for example:
+
+ String name = "Elvis Presley";
+String displayName = name != null ? name : "Unknown";
+
+ Instead you can use the Elvis operator, named for the resemblance
+ to Elvis' hair style.
+
+ ExpressionParser parser = new SpelExpressionParser();
+
+String name = parser.parseExpression("null?:'Unknown'").getValue(String.class);
+
+System.out.println(name); // 'Unknown'
+
+
+
+ Here is a more complex example.
+
+ ExpressionParser parser = new SpelExpressionParser();
+
+Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
+StandardEvaluationContext context = new StandardEvaluationContext(tesla);
+
+String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
+
+System.out.println(name); // Mike Tesla
+
+tesla.setName(null);
+
+name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
+
+System.out.println(name); // Elvis Presley
+
+
+
+ Safe Navigation operator
+
+ The Safe Navigation operator is used to avoid a
+ NullPointerException and comes from the Groovy
+ language. Typically when you have a reference to an object you might
+ need to verify that it is not null before accessing methods or
+ properties of the object. To avoid this, the safe navigation operator
+ will simply return null instead of throwing an exception.
+
+ ExpressionParser parser = new SpelExpressionParser();
+
+Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
+tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
+
+StandardEvaluationContext context = new StandardEvaluationContext(tesla);
+
+String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
+System.out.println(city); // Smiljan
+
+tesla.setPlaceOfBirth(null);
+
+city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
+
+System.out.println(city); // null - does not throw NullPointerException!!!
+
+
+
+ Collection Selection
+
+ Selection is a powerful expression language feature that allows you
+ to transform some source collection into another by selecting from its
+ entries.
+
+ Selection uses the syntax
+ ?[selectionExpression]. This will filter the
+ collection and return a new collection containing a subset of the
+ original elements. For example, selection would allow us to easily get a
+ list of Serbian inventors:
+
+ List<Inventor> list = (List<Inventor>)
+ parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext);
+
+ Selection is possible upon both lists and maps. In the former case
+ the selection criteria is evaluated against each individual list element
+ whilst against a map the selection criteria is evaluated against each
+ map entry (objects of the Java type Map.Entry). Map
+ entries have their key and value accessible as properties for use in the
+ selection.
+
+ This expression will return a new map consisting of those elements
+ of the original map where the entry value is less than 27.
+
+ Map newMap = parser.parseExpression("map.?[value<27]").getValue();
+
+ In addition to returning all the selected elements, it is possible
+ to retrieve just the first or the last value. To obtain the first entry
+ matching the selection the syntax is ^[...] whilst to
+ obtain the last matching selection the syntax is
+ $[...].
+
+
+
+ Collection Projection
+
+ Projection allows a collection to drive the evaluation of a
+ sub-expression and the result is a new collection. The syntax for
+ projection is ![projectionExpression]. Most easily
+ understood by example, suppose we have a list of inventors but want the
+ list of cities where they were born. Effectively we want to evaluate
+ 'placeOfBirth.city' for every entry in the inventor list. Using
+ projection:
+
+ // returns [ 'Smiljan', 'Idvor' ]
+List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
+
+ A map can also be used to drive projection and in this case the
+ projection expression is evaluated against each entry in the map
+ (represented as a Java Map.Entry). The result of a
+ projection across a map is a list consisting of the evaluation of the
+ projection expression against each map entry.
+
+
+
+ Expression templating
+
+ Expression templates allow a mixing of literal text with one or
+ more evaluation blocks. Each evaluation block is delimited with prefix
+ and suffix characters that you can define, a common choice is to use
+ ${} as the delimiters. For example,
+
+ String randomPhrase =
+ parser.parseExpression("random number is ${T(java.lang.Math).random()}",
+ new TemplatedParserContext()).getValue(String.class);
+
+// evaluates to "random number is 0.7038186818312008"
+
+ The string is evaluated by concatenating the literal text 'random
+ number is ' with the result of evaluating the expression inside the ${}
+ delimiter, in this case the result of calling that random() method. The
+ second argument to the method parseExpression() is of
+ the type ParserContext. The
+ ParserContext interface is used to
+ influence how the expression is parsed in order to support the
+ expression templating functionality. The definition of
+ TemplatedParserContext is shown below.
+
+ public class TemplatedParserContext implements ParserContext {
+
+ public String getExpressionPrefix() {
+ return "${";
+ }
+
+ public String getExpressionSuffix() {
+ return "}";
+ }
+
+ public boolean isTemplate() {
+ return true;
+ }
+}
+
+
+
+
+ Classes used in the examples
+
+ Inventor.java
+
+ package org.spring.samples.spel.inventor;
+
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+public class Inventor {
+
+ private String name;
+ private String nationality;
+ private String[] inventions;
+ private Date birthdate;
+ private PlaceOfBirth placeOfBirth;
+
+
+ public Inventor(String name, String nationality)
+ {
+ GregorianCalendar c= new GregorianCalendar();
+ this.name = name;
+ this.nationality = nationality;
+ this.birthdate = c.getTime();
+ }
+ public Inventor(String name, Date birthdate, String nationality) {
+ this.name = name;
+ this.nationality = nationality;
+ this.birthdate = birthdate;
+ }
+
+ public Inventor() {
+ }
+
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public String getNationality() {
+ return nationality;
+ }
+ public void setNationality(String nationality) {
+ this.nationality = nationality;
+ }
+ public Date getBirthdate() {
+ return birthdate;
+ }
+ public void setBirthdate(Date birthdate) {
+ this.birthdate = birthdate;
+ }
+ public PlaceOfBirth getPlaceOfBirth() {
+ return placeOfBirth;
+ }
+ public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
+ this.placeOfBirth = placeOfBirth;
+ }
+ public void setInventions(String[] inventions) {
+ this.inventions = inventions;
+ }
+ public String[] getInventions() {
+ return inventions;
+ }
+}
+
+
+ PlaceOfBirth.java
+
+ package org.spring.samples.spel.inventor;
+
+public class PlaceOfBirth {
+
+ private String city;
+ private String country;
+
+ public PlaceOfBirth(String city) {
+ this.city=city;
+ }
+ public PlaceOfBirth(String city, String country)
+ {
+ this(city);
+ this.country = country;
+ }
+
+
+ public String getCity() {
+ return city;
+ }
+ public void setCity(String s) {
+ this.city = s;
+ }
+ public String getCountry() {
+ return country;
+ }
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+
+
+}
+
+
+ Society.java
+
+ package org.spring.samples.spel.inventor;
+
+import java.util.*;
+
+public class Society {
+
+ private String name;
+
+ public static String Advisors = "advisors";
+ public static String President = "president";
+
+ private List<Inventor> members = new ArrayList<Inventor>();
+ private Map officers = new HashMap();
+
+ public List getMembers() {
+ return members;
+ }
+
+ public Map getOfficers() {
+ return officers;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public boolean isMember(String name)
+ {
+ boolean found = false;
+ for (Inventor inventor : members) {
+ if (inventor.getName().equals(name))
+ {
+ found = true;
+ break;
+ }
+ }
+ return found;
+ }
+
+
+}
+
+
+