From 8bda9cd2bf2ddd2e23230a4631412f5e03f6a2d2 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 7 Jan 2010 11:59:31 +0000 Subject: [PATCH] fixed StandardEvaluationContext code sample --- .../src/expressions.xml | 2416 ++++++++--------- 1 file changed, 1207 insertions(+), 1209 deletions(-) diff --git a/spring-framework-reference/src/expressions.xml b/spring-framework-reference/src/expressions.xml index 8dd4b4f745..7bb8062d9c 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; + } + + +} + +
+