1164 lines
43 KiB
XML
1164 lines
43 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
|
|
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
|
|
<chapter id="expressions">
|
|
<title>Spring Expression Language (SpEL)</title>
|
|
|
|
<section id="expressions-intro">
|
|
<title>Introduction</title>
|
|
|
|
<para>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.</para>
|
|
|
|
<para>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 an technology agnostic API allowing other
|
|
expression language implementations to be integrated should the need
|
|
arise.</para>
|
|
|
|
<para>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 was 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 <link
|
|
linkend="expressions-beandef">Expression support for defining bean
|
|
definitions.</link></para>
|
|
|
|
<para>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.</para>
|
|
</section>
|
|
|
|
<section id="expressions-features">
|
|
<title>Feature Overview</title>
|
|
|
|
<para>The expression language supports the following functionality</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>Literal expressions</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Boolean and relational operators</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Regular expressions</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Class expressions</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Accessing properties, arrays, lists, maps</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Method invocation</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Relational operators</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Assignment</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Calling constructors</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Ternary operator</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Variables</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>User defined functions</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Collection projection</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Collection selection</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>Templated expressions</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</section>
|
|
|
|
<section id="expressions-evaluation">
|
|
<title>Expression Evaluation using Spring's Expression Interface</title>
|
|
|
|
<para>This section introduces the simple use of SpEL interfaces and its
|
|
expression language. The complete language reference can be found in the
|
|
section <link lang="" linkend="expressions-language-ref">Language
|
|
Reference</link></para>
|
|
|
|
<para>The following code introduces the SpEL API to evaluate the literal
|
|
string expression 'Hello World'</para>
|
|
|
|
<para><programlisting language="java">ExpressionParser parser = new SpelExpressionParser();
|
|
Expression exp = parser.parseExpression("<emphasis role="bold">'Hello World'</emphasis>");
|
|
String message = (String) exp.getValue();</programlisting>The value of the
|
|
message variable is simply 'Hello World'.</para>
|
|
|
|
<para>The SpEL classes and interfaces you are most likely to use are
|
|
located in the packages <package>org.springframework.expression</package>
|
|
and its sub package and <package>spel.support</package>.</para>
|
|
|
|
<para>The interface <interfacename>ExpressionParser</interfacename> 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 <interfacename>Expression</interfacename> is
|
|
responsible for evaluating the previously defined expression string. There
|
|
are two exceptions that can be thrown,
|
|
<classname>ParseException</classname> and
|
|
<classname>EvaluationException</classname> when calling
|
|
'<literal>parser.parseExpression</literal>' and
|
|
'<literal>exp.getValue</literal>' respectfully.</para>
|
|
|
|
<para>SpEL supports a wide range of features, such as calling methods,
|
|
accessing properties and calling constructors.</para>
|
|
|
|
<para>As an example of method invocation, we call the 'concat' method on
|
|
the string literal</para>
|
|
|
|
<programlisting lang="" language="java">ExpressionParser parser = new SpelExpressionParser();
|
|
Expression exp = parser.parseExpression("<emphasis role="bold">'Hello World'.concat('!')</emphasis>");
|
|
String message = (String) exp.getValue();</programlisting>
|
|
|
|
<para>The value of message is now 'Hello World!'.</para>
|
|
|
|
<para>As an example of calling a JavaBean property, the String property
|
|
'Bytes' can be called as shown below</para>
|
|
|
|
<programlisting language="java">ExpressionParser parser = new SpelExpressionParser();
|
|
|
|
// invokes 'getBytes()'
|
|
Expression exp = parser.parseExpression("<emphasis role="bold">'Hello World'.bytes</emphasis>");
|
|
|
|
byte[] bytes = (byte[]) exp.getValue();</programlisting>
|
|
|
|
<para>SpEL also supports nested properties using standard 'dot' notation,
|
|
i.e. prop1.prop2.prop3 and the setting of property values</para>
|
|
|
|
<para>Public fields may also be accessed</para>
|
|
|
|
<programlisting language="java">ExpressionParser parser = new SpelExpressionParser();
|
|
|
|
// invokes 'getBytes().length'
|
|
Expression exp = parser.parseExpression("<emphasis role="bold">'Hello World'.bytes.length</emphasis>");
|
|
|
|
int length = (Integer) exp.getValue();</programlisting>
|
|
|
|
<para>The String's constructor can be called instead of using a string
|
|
literal</para>
|
|
|
|
<programlisting language="java">ExpressionParser parser = new SpelExpressionParser();
|
|
Expression exp = parser.parseExpression("<emphasis role="bold">new String('hello world').toUpperCase()</emphasis>");
|
|
String message = exp.getValue(String.class);</programlisting>
|
|
|
|
<para>Note the use of the generic method <literal>public <T> T
|
|
getValue(Class<T> desiredResultType)</literal>. Using this method
|
|
removes the need to cast the value of the expression to the desired result
|
|
type. An <classname>EvaluationException</classname> will be thrown if the
|
|
value an not be cast to the type <literal>T</literal> or converted using
|
|
the registered type converter.</para>
|
|
|
|
<para>The more common usage of SpEL is provide an expression string that
|
|
is evaluated against a specific object instance. In the following example
|
|
we retrieve the <literal>Name</literal> property from an instance of the
|
|
Inventor class.</para>
|
|
|
|
<para><programlisting language="java">// Create and set a calendar
|
|
GregorianCalendar c = new GregorianCalendar();
|
|
c.set(1856, 7, 9);
|
|
|
|
// The constructor arguments are name, birthday, and nationaltiy.
|
|
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
|
|
|
|
ExpressionParser parser = new SpelExpressionParser();
|
|
Expression exp = parser.parseExpression("<emphasis role="bold">name</emphasis>");
|
|
|
|
EvaluationContext context = new StandardEvaluationContext();
|
|
context.setRootObject(tesla);
|
|
|
|
String name = (String) exp.getValue(context);</programlisting>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. You can reuse the
|
|
same expression over and over again and set a new root object on the
|
|
evaluation context. Expressions are evaluated using reflection.</para>
|
|
|
|
<para><note>
|
|
<para>In standalone usage of SpEL you will need to create the parser
|
|
as well as provide an evaluation context. 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 will be set up for you implicitly.</para>
|
|
</note>As a final introductory example, the use of a boolean operator is
|
|
shown using the Inventor object in the previous example</para>
|
|
|
|
<programlisting language="java">Expression exp = parser.parseExpression("name == 'Nikola Tesla'");
|
|
boolean result = exp.getValue(context, Boolean.class); // evaluates to true</programlisting>
|
|
|
|
<section>
|
|
<title>The EvaluationContext interface</title>
|
|
|
|
<para>The interface <interfacename>EvaluationContext</interfacename> is
|
|
used when evaluating an expression to resolve properties, methods,
|
|
fields, and to help perform type conversion. The out-of-the-box
|
|
implementation, <classname>StandardEvaluationContext</classname>, uses
|
|
reflection to manipulate the object, caching
|
|
j<package>ava.lang.reflect</package>'s <classname>Method</classname>,
|
|
<classname>Field</classname>, and <classname>Constructor</classname>
|
|
instances for increased performance.</para>
|
|
|
|
<para>The <classname>StandardEvaluationContext</classname> is where you
|
|
specify the root object to evaluate against via the method
|
|
<methodname>setRootObject</methodname> 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
|
|
<methodname>setVariable</methodname> and
|
|
<methodname>registerFunction</methodname>. The use of variables and
|
|
functions are described in the language reference sections <link
|
|
linkend="expressions-ref-variables">Variables</link> and <link lang=""
|
|
linkend="expressions-ref-functions">Functions</link>. The
|
|
<classname>StandardEvaluationContext</classname> is also where you can
|
|
register custom <classname>ConstructorResolver</classname>s,
|
|
<classname>MethodResolver</classname>s, and
|
|
<classname>PropertyAccessor</classname>s to extend how SpEL evaluates
|
|
expressions. Please refer to the JavaDoc of these classes for more
|
|
details.</para>
|
|
|
|
<section>
|
|
<title>Type Conversion</title>
|
|
|
|
<para>By default SpEL uses the conversion service available in Spring
|
|
core
|
|
(<literal>org.springframework.core.convert.ConversionService</literal>).
|
|
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.</para>
|
|
|
|
<para>What does this mean in practice? Suppose assignment, using
|
|
<literal>setValue()</literal>, is being used to set a
|
|
<literal>List</literal> property. The type of the property is actually
|
|
<literal>List<Boolean></literal>. SpEL will recognize that the
|
|
elements of the list need to be converted to
|
|
<literal>Boolean</literal> before being placed in it. A simple
|
|
example:</para>
|
|
|
|
<programlisting>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);
|
|
</programlisting>
|
|
</section>
|
|
</section>
|
|
</section>
|
|
|
|
<section id="expressions-beandef">
|
|
<title>Expression support for defining bean definitions</title>
|
|
|
|
<para>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 <literal>#{ <expression
|
|
string> }</literal>.</para>
|
|
|
|
<section id="expressions-beandef-xml-based">
|
|
<title>XML based configuration</title>
|
|
|
|
<para>A property or constructor-arg value can be set using expressions
|
|
as shown below</para>
|
|
|
|
<programlisting language="xml"><bean id="numberGuess" class="org.spring.samples.NumberGuess">
|
|
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
|
|
|
|
<!-- other properties -->
|
|
</bean></programlisting>
|
|
|
|
<para>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.</para>
|
|
|
|
<programlisting language="xml"><bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
|
|
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
|
|
|
|
<!-- other properties -->
|
|
</bean></programlisting>
|
|
|
|
<para>You can also refer to other bean properties by name, for
|
|
example</para>
|
|
|
|
<para><programlisting language="xml"><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></programlisting></para>
|
|
</section>
|
|
|
|
<section id="expressions-beandef-annotation-based">
|
|
<title>Annotation-based configuration</title>
|
|
|
|
<para>The <literal>@Value</literal> annotation can be placed on fields,
|
|
methods and method/constructor parameters to specify a default
|
|
value.</para>
|
|
|
|
<para>Here is an example to set the default value of a field
|
|
variable</para>
|
|
|
|
<programlisting language="java">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;
|
|
}
|
|
|
|
}
|
|
|
|
</programlisting>
|
|
|
|
<para>The equivalent but on a property setter method is shown
|
|
below</para>
|
|
|
|
<programlisting language="java">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;
|
|
}
|
|
|
|
}</programlisting>
|
|
|
|
<para>Autowired methods and constructors can also use the
|
|
<literal>@Value</literal> annotation.</para>
|
|
|
|
<programlisting language="java">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;
|
|
}
|
|
|
|
// ...
|
|
}</programlisting>
|
|
|
|
<para><programlisting language="java">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;
|
|
}
|
|
|
|
// ...
|
|
}</programlisting></para>
|
|
</section>
|
|
</section>
|
|
|
|
<section id="expressions-language-ref">
|
|
<title>Language Reference</title>
|
|
|
|
<section id="expressions-ref-literal">
|
|
<title>Literal expressions</title>
|
|
|
|
<para>The types of literal expressions supported are strings, dates,
|
|
numeric values (int, real, and hex), boolean and null. String 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.</para>
|
|
|
|
<programlisting language="java">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();
|
|
</programlisting>
|
|
|
|
<para>Numbers support the use of the negative sign, exponential
|
|
notation, and decimal points. By default real numbers are parsed using
|
|
Double.parseDouble().</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Properties, Arrays, Lists, Maps, Indexers</title>
|
|
|
|
<para>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 <link
|
|
linkend="expressions-example-classes">Classes used in the
|
|
examples</link>. To navigate "down" and get Tesla's year of birth and
|
|
Pupin's city of birth the following expressions are used</para>
|
|
|
|
<programlisting lang="" language="java">// evals to 1856
|
|
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);
|
|
|
|
|
|
String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);</programlisting>
|
|
|
|
<para>Case insensitivity is allowed for the first letter of property
|
|
names. The contents of arrays and lists are obtained using square
|
|
bracket notation.</para>
|
|
|
|
<programlisting language="java">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);
|
|
</programlisting>
|
|
|
|
<para>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 literal.</para>
|
|
|
|
<programlisting lang="" language="java">// 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");
|
|
|
|
</programlisting>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Methods</title>
|
|
|
|
<para>Methods are invoked using typical Java programming syntax. You may
|
|
also invoke methods on literals. Varargs are also supported.</para>
|
|
|
|
<programlisting language="java">// 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);</programlisting>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Operators</title>
|
|
|
|
<section>
|
|
<title>Relational operators</title>
|
|
|
|
<para>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.</para>
|
|
|
|
<para><programlisting language="java">// 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);</programlisting>In
|
|
addition to standard relational operators SpEL supports the
|
|
'instanceof' and regular expression based 'matches' operator.</para>
|
|
|
|
<programlisting language="java">// 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);
|
|
|
|
</programlisting>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Logical operators</title>
|
|
|
|
<para>The logical operators that are supported are and, or, and not.
|
|
Their use is demonstrated below</para>
|
|
|
|
<para><programlisting language="java">// -- 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);</programlisting></para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Mathematical operators</title>
|
|
|
|
<para>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</para>
|
|
|
|
<para><programlisting language="java">// 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
|
|
</programlisting></para>
|
|
</section>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Assignment</title>
|
|
|
|
<para>Setting of a property is done by using the assignment operator.
|
|
This would typically be done within a call to
|
|
<literal>setValue</literal> but can also be done inside a call to
|
|
<literal>getValue</literal></para>
|
|
|
|
<programlisting language="java">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);
|
|
</programlisting>
|
|
|
|
<para></para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Types</title>
|
|
|
|
<para>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 <classname>StandardEvaluationContext</classname>
|
|
uses a <classname>TypeLocator</classname> to find types and the
|
|
<classname>StandardTypeLocator</classname> (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.</para>
|
|
|
|
<programlisting language="java">Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
|
|
|
|
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
|
|
|
|
boolean trueValue =
|
|
parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
|
|
.getValue(Boolean.class);
|
|
</programlisting>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Constructors</title>
|
|
|
|
<para>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).</para>
|
|
|
|
<programlisting language="java">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);
|
|
</programlisting>
|
|
</section>
|
|
|
|
<section id="expressions-ref-variables">
|
|
<title>Variables</title>
|
|
|
|
<para>Variables can be referenced in the expression using the syntax
|
|
#variableName. Variables are set using the method setVariable on the
|
|
StandardEvaluationContext.</para>
|
|
|
|
<programlisting language="java">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"</programlisting>
|
|
|
|
<section>
|
|
<title>The #this variable</title>
|
|
|
|
<para>The variable #this is always defined and refers to the current
|
|
evaluation object (the object against which unqualified references
|
|
will be resolved).</para>
|
|
|
|
<programlisting language="java">// 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);
|
|
|
|
</programlisting>
|
|
</section>
|
|
|
|
<!--
|
|
<section>
|
|
<title>The #root variable</title>
|
|
|
|
<para>The variable #root is always defined and refers to the
|
|
root evaluation object. This is the object against which the first unqualified
|
|
reference to a property or method is resolved.</para>
|
|
|
|
<para>It differs from #this in that #this typically varies throughout the
|
|
evaluation of an expression, whilst #root remains constant.
|
|
It can be useful when writing a selection criteria, where the decision
|
|
needs to be made based on some property of the root object rather than the
|
|
current collection element. For example:</para>
|
|
|
|
<programlisting language="java">List selection = (List)parser.parseExpression("#someList.?[#root.supports(#this)]").getValue();
|
|
</programlisting>
|
|
</section>
|
|
-->
|
|
</section>
|
|
|
|
<section id="expressions-ref-functions">
|
|
<title>Functions</title>
|
|
|
|
<para>You can extend SpEL by registering user defined functions that can
|
|
be called within the expression string. The function is registered with
|
|
the <classname>StandardEvaluationContext</classname> using the
|
|
method</para>
|
|
|
|
<programlisting language="java">public void registerFunction(String name, Method m)</programlisting>
|
|
|
|
<para>A reference to a Java Method provides the implementation of the
|
|
function. For example, a utility method to reverse a string is shown
|
|
below.</para>
|
|
|
|
<programlisting>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();
|
|
}
|
|
}</programlisting>
|
|
|
|
<para>This method is then registered with the evaluation context and can
|
|
be used within an expression string</para>
|
|
|
|
<programlisting language="java">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);</programlisting>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Ternary Operator (If-Then-Else)</title>
|
|
|
|
<para>You can use the ternary operator for performing if-then-else
|
|
conditional logic inside the expression. A minimal example is;</para>
|
|
|
|
<programlisting language="java">String falseString =
|
|
parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class);</programlisting>
|
|
|
|
<para>In this case, the boolean false results in returning the string
|
|
value 'falseExp'. A less artificial example is shown below.</para>
|
|
|
|
<programlisting language="java">parser.parseExpression("Name").setValue(societyContext, "IEEE");
|
|
societyContext.setVariable("queryName", "Nikola Tesla");
|
|
|
|
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
|
|
"+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";
|
|
|
|
String queryResultString =
|
|
parser.parseExpression(expression).getValue(societyContext, String.class);
|
|
// queryResultString = "Nikola Tesla is a member of the IEEE Society"</programlisting>
|
|
|
|
<para>Also see the next section on the Elvis operator for an even
|
|
shorter syntax for the ternary operator</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>The Elvis Operator</title>
|
|
|
|
<para>The Elvis operator is a shortening of the ternary operator syntax
|
|
and is used in the <ulink
|
|
url="http://groovy.codehaus.org/Operators#Operators-ElvisOperator(%3F%3A)">Groovy</ulink>
|
|
language. The ternary operator syntax you usually have to repeat a
|
|
variable twice, for example</para>
|
|
|
|
<programlisting>String name = "Elvis Presley";
|
|
String displayName = name != null ? name : "Unknown";</programlisting>
|
|
|
|
<para>Instead you can use the Elvis operator, named for the resemblance
|
|
to Elvis' hair style. </para>
|
|
|
|
<programlisting>ExpressionParser parser = new SpelExpressionParser();
|
|
|
|
String name = parser.parseExpression("null?:'Unknown'").getValue(String.class);
|
|
|
|
System.out.println(name); // 'Unknown'
|
|
|
|
</programlisting>
|
|
|
|
<para>Here is a more complex example </para>
|
|
|
|
<programlisting>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</programlisting>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Safe Navigation operator</title>
|
|
|
|
<para>The Safe Navigation operator is used to avoid a
|
|
<literal>NullPointerException</literal> and comes from the <ulink
|
|
url="http://groovy.codehaus.org/Operators#Operators-SafeNavigationOperator(%3F.)">Groovy</ulink>
|
|
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.</para>
|
|
|
|
<programlisting>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!!!</programlisting>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Collection Selection</title>
|
|
|
|
<para>Selection is a powerful expression language feature that allow you
|
|
to transform some source collection into another by selecting from its
|
|
entries.</para>
|
|
|
|
<para>Selection uses the syntax
|
|
<literal>?[selectionExpression]</literal>. 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:</para>
|
|
|
|
<programlisting language="java">List<Inventor> list = (List<Inventor>)
|
|
parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext);</programlisting>
|
|
|
|
<para>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 <literal>Map.Entry</literal>). Map
|
|
entries have their key and value accessible as properties for use in the
|
|
selection.</para>
|
|
|
|
<para>This expression will return a new map consisting of those elements
|
|
of the original map where the entry value is less than 27.</para>
|
|
|
|
<programlisting language="java">Map newMap = parser.parseExpression("map.?[value<27]").getValue();</programlisting>
|
|
|
|
<para>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 <literal>^[...]</literal> whilst to
|
|
obtain the last matching selection the syntax is
|
|
<literal>$[...]</literal>.</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Collection Projection</title>
|
|
|
|
<para>Projection allows a collection to drive the evaluation of a
|
|
sub-expression and the result is a new collection. The syntax for
|
|
projection is <literal>![projectionExpression]</literal>. 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:</para>
|
|
|
|
<programlisting language="java">// returns [ 'Smiljan', 'Idvor' ]
|
|
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");</programlisting>
|
|
|
|
<para>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 <literal>Map.Entry</literal>). The result of a
|
|
projection across a map is a list consisting of the evaluation of the
|
|
projection expression against each map entry.</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Expression templating</title>
|
|
|
|
<para>Expression templates allow a mixing of literal text with one or
|
|
more evaluation blocks. Each evaluation block is delimited with a prefix
|
|
and suffix characters that you can define, a common choice is to use
|
|
<literal>${} </literal>as the delimiters. For example,</para>
|
|
|
|
<programlisting language="java">String randomPhrase =
|
|
parser.parseExpression("random number is ${T(java.lang.Math).random()}",
|
|
new TemplatedParserContext()).getValue(String.class);
|
|
|
|
// evaluates to "random number is 0.7038186818312008"</programlisting>
|
|
|
|
<para>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 <literal>parseExpression()</literal> of
|
|
the type <interfacename>ParserContext</interfacename>. The
|
|
<interfacename>ParserContext</interfacename> interface is used to
|
|
influence how the expression is parsed in order to support the
|
|
expression templating functionality. The definition of
|
|
<classname>TemplatedParserContext</classname> is shown below</para>
|
|
|
|
<programlisting language="java">public class TemplatedParserContext implements ParserContext {
|
|
|
|
public String getExpressionPrefix() {
|
|
return "${";
|
|
}
|
|
|
|
public String getExpressionSuffix() {
|
|
return "}";
|
|
}
|
|
|
|
public boolean isTemplate() {
|
|
return true;
|
|
}
|
|
}</programlisting>
|
|
</section>
|
|
</section>
|
|
|
|
<section id="expressions-example-classes">
|
|
<title>Classes used in the examples</title>
|
|
|
|
<para>Inventor.java</para>
|
|
|
|
<programlisting language="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;
|
|
}
|
|
}
|
|
</programlisting>
|
|
|
|
<para>PlaceOfBirth.java</para>
|
|
|
|
<programlisting language="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;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
</programlisting>
|
|
|
|
<para>Society.java</para>
|
|
|
|
<programlisting lang="">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;
|
|
}
|
|
|
|
|
|
}
|
|
</programlisting>
|
|
</section>
|
|
</chapter>
|