1<?xml version="1.0" encoding="UTF-8"?> 2<chapter xml:id="expressions" 3 xmlns="http://docbook.org/ns/docbook" version="5.0" 4 xmlns:xl="http://www.w3.org/1999/xlink" 5 xmlns:xi="http://www.w3.org/2001/XInclude" 6 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 7 xsi:schemaLocation=" 8 http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd 9 http://www.w3.org/1999/xlink http://www.docbook.org/xml/5.0/xsd/xlink.xsd"> 10 <title>Spring Expression Language (SpEL)</title> 11 12 <section xml:id="expressions-intro"> 13 <title>Introduction</title> 14 15 <para>The Spring Expression Language (SpEL for short) is a powerful 16 expression language that supports querying and manipulating an object 17 graph at runtime. The language syntax is similar to Unified EL but offers 18 additional features, most notably method invocation and basic string 19 templating functionality.</para> 20 21 <para>While there are several other Java expression languages available, 22 OGNL, MVEL, and JBoss EL, to name a few, the Spring Expression Language 23 was created to provide the Spring community with a single well supported 24 expression language that can be used across all the products in the Spring 25 portfolio. Its language features are driven by the requirements of the 26 projects in the Spring portfolio, including tooling requirements for code 27 completion support within the eclipse based SpringSource Tool Suite. That 28 said, SpEL is based on a technology agnostic API allowing other 29 expression language implementations to be integrated should the need 30 arise.</para> 31 32 <para>While SpEL serves as the foundation for expression evaluation within 33 the Spring portfolio, it is not directly tied to Spring and can be used 34 independently. In order to be self contained, many of the examples in this 35 chapter use SpEL as if it were an independent expression language. This 36 requires creating a few bootstrapping infrastructure classes such as the 37 parser. Most Spring users will not need to deal with this infrastructure 38 and will instead only author expression strings for evaluation. An example 39 of this typical use is the integration of SpEL into creating XML or 40 annotated based bean definitions as shown in the section <link 41 linkend="expressions-beandef">Expression support for defining bean 42 definitions.</link></para> 43 44 <para>This chapter covers the features of the expression language, its 45 API, and its language syntax. In several places an Inventor and Inventor's 46 Society class are used as the target objects for expression evaluation. 47 These class declarations and the data used to populate them are listed at 48 the end of the chapter.</para> 49 </section> 50 51 <section xml:id="expressions-features"> 52 <title>Feature Overview</title> 53 54 <para>The expression language supports the following functionality</para> 55 56 <itemizedlist> 57 <listitem> 58 <para>Literal expressions</para> 59 </listitem> 60 61 <listitem> 62 <para>Boolean and relational operators</para> 63 </listitem> 64 65 <listitem> 66 <para>Regular expressions</para> 67 </listitem> 68 69 <listitem> 70 <para>Class expressions</para> 71 </listitem> 72 73 <listitem> 74 <para>Accessing properties, arrays, lists, maps</para> 75 </listitem> 76 77 <listitem> 78 <para>Method invocation</para> 79 </listitem> 80 81 <listitem> 82 <para>Relational operators</para> 83 </listitem> 84 85 <listitem> 86 <para>Assignment</para> 87 </listitem> 88 89 <listitem> 90 <para>Calling constructors</para> 91 </listitem> 92 93 <listitem> 94 <para>Bean references</para> 95 </listitem> 96 97 <listitem> 98 <para>Array construction</para> 99 </listitem> 100 101 <listitem> 102 <para>Inline lists</para> 103 </listitem> 104 105 <listitem> 106 <para>Ternary operator</para> 107 </listitem> 108 109 <listitem> 110 <para>Variables</para> 111 </listitem> 112 113 <listitem> 114 <para>User defined functions</para> 115 </listitem> 116 117 <listitem> 118 <para>Collection projection</para> 119 </listitem> 120 121 <listitem> 122 <para>Collection selection</para> 123 </listitem> 124 125 <listitem> 126 <para>Templated expressions</para> 127 </listitem> 128 </itemizedlist> 129 </section> 130 131 <section xml:id="expressions-evaluation"> 132 <title>Expression Evaluation using Spring's Expression Interface</title> 133 134 <para>This section introduces the simple use of SpEL interfaces and its 135 expression language. The complete language reference can be found in the 136 section <link linkend="expressions-language-ref">Language 137 Reference</link>.</para> 138 139 <para>The following code introduces the SpEL API to evaluate the literal 140 string expression 'Hello World'.</para> 141 142 <para><programlisting language="java">ExpressionParser parser = new SpelExpressionParser(); 143Expression exp = parser.parseExpression("<emphasis role="bold">'Hello World'</emphasis>"); 144String message = (String) exp.getValue();</programlisting>The value of the 145 message variable is simply 'Hello World'.</para> 146 147 <para>The SpEL classes and interfaces you are most likely to use are 148 located in the packages <package>org.springframework.expression</package> 149 and its sub packages and <package>spel.support</package>.</para> 150 151 <para>The interface <interfacename>ExpressionParser</interfacename> is 152 responsible for parsing an expression string. In this example the 153 expression string is a string literal denoted by the surrounding single 154 quotes. The interface <interfacename>Expression</interfacename> is 155 responsible for evaluating the previously defined expression string. There 156 are two exceptions that can be thrown, 157 <classname>ParseException</classname> and 158 <classname>EvaluationException</classname> when calling 159 '<literal>parser.parseExpression</literal>' and 160 '<literal>exp.getValue</literal>' respectively.</para> 161 162 <para>SpEL supports a wide range of features, such as calling methods, 163 accessing properties, and calling constructors.</para> 164 165 <para>As an example of method invocation, we call the 'concat' method on 166 the string literal.</para> 167 168 <programlisting language="java">ExpressionParser parser = new SpelExpressionParser(); 169Expression exp = parser.parseExpression("<emphasis role="bold">'Hello World'.concat('!')</emphasis>"); 170String message = (String) exp.getValue();</programlisting> 171 172 <para>The value of message is now 'Hello World!'.</para> 173 174 <para>As an example of calling a JavaBean property, the String property 175 'Bytes' can be called as shown below.</para> 176 177 <programlisting language="java">ExpressionParser parser = new SpelExpressionParser(); 178 179// invokes 'getBytes()' 180Expression exp = parser.parseExpression("<emphasis role="bold">'Hello World'.bytes</emphasis>"); 181 182byte[] bytes = (byte[]) exp.getValue();</programlisting> 183 184 <para>SpEL also supports nested properties using standard 'dot' notation, 185 i.e. prop1.prop2.prop3 and the setting of property values</para> 186 187 <para>Public fields may also be accessed.</para> 188 189 <programlisting language="java">ExpressionParser parser = new SpelExpressionParser(); 190 191// invokes 'getBytes().length' 192Expression exp = parser.parseExpression("<emphasis role="bold">'Hello World'.bytes.length</emphasis>"); 193 194int length = (Integer) exp.getValue();</programlisting> 195 196 <para>The String's constructor can be called instead of using a string 197 literal.</para> 198 199 <programlisting language="java">ExpressionParser parser = new SpelExpressionParser(); 200Expression exp = parser.parseExpression("<emphasis role="bold">new String('hello world').toUpperCase()</emphasis>"); 201String message = exp.getValue(String.class);</programlisting> 202 203 <para>Note the use of the generic method <literal>public <T> T 204 getValue(Class<T> desiredResultType)</literal>. Using this method 205 removes the need to cast the value of the expression to the desired result 206 type. An <classname>EvaluationException</classname> will be thrown if the 207 value cannot be cast to the type <literal>T</literal> or converted using 208 the registered type converter.</para> 209 210 <para>The more common usage of SpEL is to provide an expression string that 211 is evaluated against a specific object instance (called the root object). 212 There are two options here and which to choose depends on whether the object 213 against which the expression is being evaluated will be changing with each 214 call to evaluate the expression. In the following example 215 we retrieve the <literal>name</literal> property from an instance of the 216 Inventor class.</para> 217 218 <programlisting language="java">// Create and set a calendar 219GregorianCalendar c = new GregorianCalendar(); 220c.set(1856, 7, 9); 221 222// The constructor arguments are name, birthday, and nationality. 223Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); 224 225ExpressionParser parser = new SpelExpressionParser(); 226Expression exp = parser.parseExpression("<emphasis role="bold">name</emphasis>"); 227EvaluationContext context = new StandardEvaluationContext(tesla); 228 229String name = (String) exp.getValue(context);</programlisting> 230<para>In the last 231 line, the value of the string variable 'name' will be set to "Nikola 232 Tesla". The class StandardEvaluationContext is where you can specify which 233 object the "name" property will be evaluated against. This is the mechanism 234 to use if the root object is unlikely to change, it can simply be set once 235 in the evaluation context. If the root object is likely to change 236 repeatedly, it can be supplied on each call to <literal>getValue</literal>, 237 as this next example shows:</para> 238 239 <programlisting language="java">/ Create and set a calendar 240GregorianCalendar c = new GregorianCalendar(); 241c.set(1856, 7, 9); 242 243// The constructor arguments are name, birthday, and nationality. 244Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); 245 246ExpressionParser parser = new SpelExpressionParser(); 247Expression exp = parser.parseExpression("<emphasis role="bold">name</emphasis>"); 248 249String name = (String) exp.getValue(tesla); 250 </programlisting><para>In this case the inventor <literal>tesla</literal> has been 251 supplied directly to <literal>getValue</literal> and the expression 252 evaluation infrastructure creates and manages a default evaluation context 253 internally - it did not require one to be supplied.</para> 254 255 <para>The StandardEvaluationContext is relatively expensive to construct and 256 during repeated usage it builds up cached state that enables subsequent 257 expression evaluations to be performed more quickly. For this reason it is 258 better to cache and reuse them where possible, rather than construct a new 259 one for each expression evaluation. 260 </para> 261 <para>In some cases it can be desirable to use a configured evaluation context and 262 yet still supply a different root object on each call to <literal>getValue</literal>. 263 <literal>getValue</literal> allows both to be specified on the same call. 264 In these situations the root object passed on the call is considered to override 265 any (which maybe null) specified on the evaluation context.</para> 266 267 <para> 268 <note> 269 <para>In standalone usage of SpEL there is a need to create the parser, 270 parse expressions and perhaps provide evaluation contexts and a root 271 context object. However, more common usage 272 is to provide only the SpEL expression string as part of a 273 configuration file, for example for Spring bean or Spring Web Flow 274 definitions. In this case, the parser, evaluation context, root object 275 and any predefined variables are all set up implicitly, requiring 276 the user to specify nothing other than the expressions.</para> 277 </note> 278 As a final introductory example, the use of a boolean operator is 279 shown using the Inventor object in the previous example.</para> 280 281 <programlisting language="java">Expression exp = parser.parseExpression("name == 'Nikola Tesla'"); 282boolean result = exp.getValue(context, Boolean.class); // evaluates to true</programlisting> 283 284 <section xml:id="expressions-evaluation-context"> 285 <title>The EvaluationContext interface</title> 286 287 <para>The interface <interfacename>EvaluationContext</interfacename> is 288 used when evaluating an expression to resolve properties, methods, 289 fields, and to help perform type conversion. The out-of-the-box 290 implementation, <classname>StandardEvaluationContext</classname>, uses 291 reflection to manipulate the object, caching 292 <package>java.lang.reflect</package>'s <classname>Method</classname>, 293 <classname>Field</classname>, and <classname>Constructor</classname> 294 instances for increased performance.</para> 295 296 <para>The <classname>StandardEvaluationContext</classname> is where you 297 may specify the root object to evaluate against via the method 298 <methodname>setRootObject()</methodname> or passing the root object into 299 the constructor. You can also specify variables and functions that 300 will be used in the expression using the methods 301 <methodname>setVariable()</methodname> and 302 <methodname>registerFunction()</methodname>. The use of variables and 303 functions are described in the language reference sections <link 304 linkend="expressions-ref-variables">Variables</link> and <link 305 linkend="expressions-ref-functions">Functions</link>. The 306 <classname>StandardEvaluationContext</classname> is also where you can 307 register custom <classname>ConstructorResolver</classname>s, 308 <classname>MethodResolver</classname>s, and 309 <classname>PropertyAccessor</classname>s to extend how SpEL evaluates 310 expressions. Please refer to the JavaDoc of these classes for more 311 details.</para> 312 313 <section xml:id="expressions-type-conversion"> 314 <title>Type Conversion</title> 315 316 <para>By default SpEL uses the conversion service available in Spring 317 core 318 (<literal>org.springframework.core.convert.ConversionService</literal>). 319 This conversion service comes with many converters built in for common 320 conversions but is also fully extensible so custom conversions between 321 types can be added. Additionally it has the key capability that it is 322 generics aware. This means that when working with generic types in 323 expressions, SpEL will attempt conversions to maintain type 324 correctness for any objects it encounters.</para> 325 326 <para>What does this mean in practice? Suppose assignment, using 327 <literal>setValue()</literal>, is being used to set a 328 <literal>List</literal> property. The type of the property is actually 329 <literal>List<Boolean></literal>. SpEL will recognize that the 330 elements of the list need to be converted to 331 <literal>Boolean</literal> before being placed in it. A simple 332 example:</para> 333 334 <programlisting language="java">class Simple { 335 public List<Boolean> booleanList = new ArrayList<Boolean>(); 336} 337 338Simple simple = new Simple(); 339 340simple.booleanList.add(true); 341 342StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple); 343 344// false is passed in here as a string. SpEL and the conversion service will 345// correctly recognize that it needs to be a Boolean and convert it 346parser.parseExpression("booleanList[0]").setValue(simpleContext, "false"); 347 348// b will be false 349Boolean b = simple.booleanList.get(0); 350 </programlisting> 351 </section> 352 </section> 353 </section> 354 355 <section xml:id="expressions-beandef"> 356 <title>Expression support for defining bean definitions</title> 357 358 <para>SpEL expressions can be used with XML or annotation based 359 configuration metadata for defining BeanDefinitions. In both cases the 360 syntax to define the expression is of the form <literal>#{ <expression 361 string> }</literal>.</para> 362 363 <section xml:id="expressions-beandef-xml-based"> 364 <title>XML based configuration</title> 365 366 <para>A property or constructor-arg value can be set using expressions 367 as shown below</para> 368 369 <programlisting language="xml"><bean id="numberGuess" class="org.spring.samples.NumberGuess"> 370 <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> 371 372 <!-- other properties --> 373</bean></programlisting> 374 375 <para>The variable 'systemProperties' is predefined, so you can use it 376 in your expressions as shown below. Note that you do not have to prefix 377 the predefined variable with the '#' symbol in this context.</para> 378 379 <programlisting language="xml"><bean id="taxCalculator" class="org.spring.samples.TaxCalculator"> 380 <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/> 381 382 <!-- other properties --> 383</bean></programlisting> 384 385 <para>You can also refer to other bean properties by name, for 386 example.</para> 387 388 <para><programlisting language="xml"><bean id="numberGuess" class="org.spring.samples.NumberGuess"> 389 <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> 390 391 <!-- other properties --> 392</bean> 393 394 395<bean id="shapeGuess" class="org.spring.samples.ShapeGuess"> 396 <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/> 397 398 <!-- other properties --> 399</bean></programlisting></para> 400 </section> 401 402 <section xml:id="expressions-beandef-annotation-based"> 403 <title>Annotation-based configuration</title> 404 405 <para>The <literal>@Value</literal> annotation can be placed on fields, 406 methods and method/constructor parameters to specify a default 407 value.</para> 408 409 <para>Here is an example to set the default value of a field 410 variable.</para> 411 412 <programlisting language="java">public static class FieldValueTestBean 413 414 @Value("#{ systemProperties['user.region'] }") 415 private String defaultLocale; 416 417 public void setDefaultLocale(String defaultLocale) 418 { 419 this.defaultLocale = defaultLocale; 420 } 421 422 public String getDefaultLocale() 423 { 424 return this.defaultLocale; 425 } 426 427} 428 429</programlisting> 430 431 <para>The equivalent but on a property setter method is shown 432 below.</para> 433 434 <programlisting language="java">public static class PropertyValueTestBean 435 436 private String defaultLocale; 437 438 @Value("#{ systemProperties['user.region'] }") 439 public void setDefaultLocale(String defaultLocale) 440 { 441 this.defaultLocale = defaultLocale; 442 } 443 444 public String getDefaultLocale() 445 { 446 return this.defaultLocale; 447 } 448 449}</programlisting> 450 451 <para>Autowired methods and constructors can also use the 452 <literal>@Value</literal> annotation.</para> 453 454 <programlisting language="java">public class SimpleMovieLister { 455 456 private MovieFinder movieFinder; 457 private String defaultLocale; 458 459 @Autowired 460 public void configure(MovieFinder movieFinder, 461 @Value("#{ systemProperties['user.region'] }"} String defaultLocale) { 462 this.movieFinder = movieFinder; 463 this.defaultLocale = defaultLocale; 464 } 465 466 // ... 467}</programlisting> 468 469 <para><programlisting language="java">public class MovieRecommender { 470 471 private String defaultLocale; 472 473 private CustomerPreferenceDao customerPreferenceDao; 474 475 @Autowired 476 public MovieRecommender(CustomerPreferenceDao customerPreferenceDao, 477 @Value("#{systemProperties['user.country']}"} String defaultLocale) { 478 this.customerPreferenceDao = customerPreferenceDao; 479 this.defaultLocale = defaultLocale; 480 } 481 482 // ... 483}</programlisting></para> 484 </section> 485 </section> 486 487 <section xml:id="expressions-language-ref"> 488 <title>Language Reference</title> 489 490 <section xml:id="expressions-ref-literal"> 491 <title>Literal expressions</title> 492 493 <para>The types of literal expressions supported are strings, dates, 494 numeric values (int, real, and hex), boolean and null. Strings are 495 delimited by single quotes. To put a single quote itself in a string use 496 two single quote characters. The following listing shows simple usage of 497 literals. Typically they would not be used in isolation like this, but 498 as part of a more complex expression, for example using a literal on one 499 side of a logical comparison operator.</para> 500 501 <programlisting language="java">ExpressionParser parser = new SpelExpressionParser(); 502 503// evals to "Hello World" 504String helloWorld = (String) parser.parseExpression("'Hello World'").getValue(); 505 506double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue(); 507 508// evals to 2147483647 509int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue(); 510 511boolean trueValue = (Boolean) parser.parseExpression("true").getValue(); 512 513Object nullValue = parser.parseExpression("null").getValue(); 514</programlisting> 515 516 <para>Numbers support the use of the negative sign, exponential 517 notation, and decimal points. By default real numbers are parsed using 518 Double.parseDouble().</para> 519 </section> 520 521 <section xml:id="expressions-properties-arrays"> 522 <title>Properties, Arrays, Lists, Maps, Indexers</title> 523 524 <para>Navigating with property references is easy, just use a period to 525 indicate a nested property value. The instances of Inventor class, pupin 526 and tesla, were populated with data listed in the section <link 527 linkend="expressions-example-classes">Classes used in the 528 examples</link>. To navigate "down" and get Tesla's year of birth and 529 Pupin's city of birth the following expressions are used.</para> 530 531 <programlisting language="java">// evals to 1856 532int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); 533 534 535String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);</programlisting> 536 537 <para>Case insensitivity is allowed for the first letter of property 538 names. The contents of arrays and lists are obtained using square 539 bracket notation.</para> 540 541 <programlisting language="java">ExpressionParser parser = new SpelExpressionParser(); 542 543// Inventions Array 544StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla); 545 546// evaluates to "Induction motor" 547String invention = parser.parseExpression("inventions[3]").getValue(teslaContext, 548 String.class); 549 550 551// Members List 552StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee); 553 554// evaluates to "Nikola Tesla" 555String name = parser.parseExpression("Members[0].Name").getValue(societyContext, String.class); 556 557// List and Array navigation 558// evaluates to "Wireless communication" 559String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(societyContext, 560 String.class); 561</programlisting> 562 563 <para>The contents of maps are obtained by specifying the literal key 564 value within the brackets. In this case, because keys for the Officers 565 map are strings, we can specify string literals.</para> 566 567 <programlisting language="java">// Officer's Dictionary 568 569Inventor pupin = parser.parseExpression("Officers['president']").getValue(societyContext, 570 Inventor.class); 571 572// evaluates to "Idvor" 573String city = 574 parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(societyContext, 575 String.class); 576 577// setting values 578parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, 579 "Croatia"); 580 581</programlisting> 582 </section> 583 <section xml:id="expressions-inline-lists"> 584 <title>Inline lists</title> 585 586 <para>Lists can be expressed directly in an expression using {} notation. 587 </para> 588 589 <programlisting language="java"> 590// evaluates to a Java list containing the four numbers 591List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context); 592 593List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context); 594</programlisting> 595 <para>{} by itself means an empty list. For performance reasons, if the 596 list is itself entirely composed of fixed literals then a constant list is created 597 to represent the expression, rather than building a new list on each evaluation.</para> 598 </section> 599 600 <section xml:id="expressions-array-construction"> 601 <title>Array construction</title> 602 603 <para>Arrays can be built using the familiar Java syntax, optionally 604 supplying an initializer to have the array populated at construction time. 605 </para> 606 607 <programlisting language="java">int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context); 608 609// Array with initializer 610int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context); 611 612// Multi dimensional array 613int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context); 614</programlisting> 615 <para>It is not currently allowed to supply an initializer when constructing 616 a multi-dimensional array.</para> 617 </section> 618 619 <section xml:id="expressions-methods"> 620 <title>Methods</title> 621 622 <para>Methods are invoked using typical Java programming syntax. You may 623 also invoke methods on literals. Varargs are also supported.</para> 624 625 <programlisting language="java">// string literal, evaluates to "bc" 626String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class); 627 628// evaluates to true 629boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(societyContext, 630 Boolean.class);</programlisting> 631 </section> 632 633 <section xml:id="expressions-operators"> 634 <title>Operators</title> 635 636 <section xml:id="expressions-operators-relational"> 637 <title>Relational operators</title> 638 639 <para>The relational operators; equal, not equal, less than, less than 640 or equal, greater than, and greater than or equal are supported using 641 standard operator notation.</para> 642 643 <para><programlisting language="java">// evaluates to true 644boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class); 645 646// evaluates to false 647boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class); 648 649// evaluates to true 650boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);</programlisting> 651 In addition to standard relational operators SpEL supports the 652 'instanceof' and regular expression based 'matches' operator.</para> 653 654 <programlisting language="java">// evaluates to false 655boolean falseValue = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class); 656 657// evaluates to true 658boolean trueValue = 659 parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); 660 661//evaluates to false 662boolean falseValue = 663 parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); 664 665</programlisting> 666 <para>Each symbolic operator can also be specified as a purely alphabetic equivalent. This avoids 667 problems where the symbols used have special meaning for the document type in which 668 the expression is embedded (eg. an XML document). The textual equivalents are shown 669 here: lt ('<'), gt ('>'), le ('<='), ge ('>='), 670 eq ('=='), ne ('!='), div ('/'), mod ('%'), not ('!'). 671 These are case insensitive.</para> 672 </section> 673 674 <section xml:id="expressions-operators-logical"> 675 <title>Logical operators</title> 676 677 <para>The logical operators that are supported are and, or, and not. 678 Their use is demonstrated below.</para> 679 680 <para><programlisting language="java">// -- AND -- 681 682// evaluates to false 683boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class); 684 685// evaluates to true 686String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"; 687boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); 688 689// -- OR -- 690 691// evaluates to true 692boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class); 693 694// evaluates to true 695String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"; 696boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); 697 698// -- NOT -- 699 700// evaluates to false 701boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class); 702 703 704// -- AND and NOT -- 705String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"; 706boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);</programlisting></para> 707 </section> 708 709 <section xml:id="expressions-operators-mathematical"> 710 <title>Mathematical operators</title> 711 712 <para>The addition operator can be used on numbers, strings and dates. 713 Subtraction can be used on numbers and dates. Multiplication and 714 division can be used only on numbers. Other mathematical operators 715 supported are modulus (%) and exponential power (^). Standard operator 716 precedence is enforced. These operators are demonstrated below.</para> 717 718 <para><programlisting language="java">// Addition 719int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 720 721String testString = 722 parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class); // 'test string' 723 724// Subtraction 725int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4 726 727double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000 728 729// Multiplication 730int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6 731 732double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0 733 734// Division 735int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2 736 737double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0 738 739// Modulus 740int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3 741 742int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1 743 744// Operator precedence 745int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21 746</programlisting></para> 747 </section> 748 </section> 749 750 <section xml:id="expressions-assignment"> 751 <title>Assignment</title> 752 753 <para>Setting of a property is done by using the assignment operator. 754 This would typically be done within a call to 755 <literal>setValue</literal> but can also be done inside a call to 756 <literal>getValue</literal>.</para> 757 758 <programlisting language="java">Inventor inventor = new Inventor(); 759StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor); 760 761parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2"); 762 763// alternatively 764 765String aleks = parser.parseExpression("Name = 'Alexandar Seovic'").getValue(inventorContext, 766 String.class); 767</programlisting> 768 769 <para></para> 770 </section> 771 772 <section xml:id="expressions-types"> 773 <title>Types</title> 774 775 <para>The special 'T' operator can be used to specify an instance of 776 java.lang.Class (the 'type'). Static methods are invoked using this 777 operator as well. The <classname>StandardEvaluationContext</classname> 778 uses a <classname>TypeLocator</classname> to find types and the 779 <classname>StandardTypeLocator</classname> (which can be replaced) is 780 built with an understanding of the java.lang package. This means T() 781 references to types within java.lang do not need to be fully qualified, 782 but all other type references must be.</para> 783 784 <programlisting language="java">Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class); 785 786Class stringClass = parser.parseExpression("T(String)").getValue(Class.class); 787 788boolean trueValue = 789 parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR") 790 .getValue(Boolean.class); 791</programlisting> 792 </section> 793 794 <section xml:id="expressions-constructors"> 795 <title>Constructors</title> 796 797 <para>Constructors can be invoked using the new operator. The fully 798 qualified class name should be used for all but the primitive type and 799 String (where int, float, etc, can be used).</para> 800 801 <programlisting language="java">Inventor einstein = 802 p.parseExpression("new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 803 'German')") 804 .getValue(Inventor.class); 805 806//create new inventor instance within add method of List 807p.parseExpression("Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 808 'German'))") 809 .getValue(societyContext); 810</programlisting> 811 </section> 812 813 <section xml:id="expressions-ref-variables"> 814 <title>Variables</title> 815 816 <para>Variables can be referenced in the expression using the syntax 817 #variableName. Variables are set using the method setVariable on the 818 StandardEvaluationContext.</para> 819 820 <programlisting language="java">Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); 821StandardEvaluationContext context = new StandardEvaluationContext(tesla); 822context.setVariable("newName", "Mike Tesla"); 823 824parser.parseExpression("Name = #newName").getValue(context); 825 826System.out.println(tesla.getName()) // "Mike Tesla"</programlisting> 827 828 <section xml:id="expressions-this-root"> 829 <title>The #this and #root variables</title> 830 831 <para>The variable #this is always defined and refers to the current 832 evaluation object (against which unqualified references are resolved). 833 The variable #root is always defined and refers to the root 834 context object. Although #this may vary as components of an expression 835 are evaluated, #root always refers to the root.</para> 836 837 <programlisting language="java">// create an array of integers 838List<Integer> primes = new ArrayList<Integer>(); 839primes.addAll(Arrays.asList(2,3,5,7,11,13,17)); 840 841// create parser and set variable 'primes' as the array of integers 842ExpressionParser parser = new SpelExpressionParser(); 843StandardEvaluationContext context = new StandardEvaluationContext(); 844context.setVariable("primes",primes); 845 846// all prime numbers > 10 from the list (using selection ?{...}) 847// evaluates to [11, 13, 17] 848List<Integer> primesGreaterThanTen = 849 (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context); 850 851</programlisting> 852 </section> 853 854 <!-- 855 <section xml:id="expressions-root"> 856 <title>The #root variable</title> 857 858 <para>The variable #root is always defined and refers to the 859 root evaluation object. This is the object against which the first unqualified 860 reference to a property or method is resolved.</para> 861 862 <para>It differs from #this in that #this typically varies throughout the 863 evaluation of an expression, whilst #root remains constant. 864 It can be useful when writing a selection criteria, where the decision 865 needs to be made based on some property of the root object rather than the 866 current collection element. For example:</para> 867 868 <programlisting language="java">List selection = (List)parser.parseExpression("#someList.?[#root.supports(#this)]").getValue(); 869</programlisting> 870 </section> 871 --> 872 </section> 873 874 <section xml:id="expressions-ref-functions"> 875 <title>Functions</title> 876 877 <para>You can extend SpEL by registering user defined functions that can 878 be called within the expression string. The function is registered with 879 the <classname>StandardEvaluationContext</classname> using the 880 method.</para> 881 882 <programlisting language="java">public void registerFunction(String name, Method m)</programlisting> 883 884 <para>A reference to a Java Method provides the implementation of the 885 function. For example, a utility method to reverse a string is shown 886 below.</para> 887 888 <programlisting>public abstract class StringUtils { 889 890 public static String reverseString(String input) { 891 StringBuilder backwards = new StringBuilder(); 892 for (int i = 0; i < input.length(); i++) 893 backwards.append(input.charAt(input.length() - 1 - i)); 894 } 895 return backwards.toString(); 896 } 897}</programlisting> 898 899 <para>This method is then registered with the evaluation context and can 900 be used within an expression string.</para> 901 902 <programlisting language="java">ExpressionParser parser = new SpelExpressionParser(); 903StandardEvaluationContext context = new StandardEvaluationContext(); 904 905context.registerFunction("reverseString", 906 StringUtils.class.getDeclaredMethod("reverseString", 907 new Class[] { String.class })); 908 909String helloWorldReversed = 910 parser.parseExpression("#reverseString('hello')").getValue(context, String.class);</programlisting> 911 </section> 912 913 <section xml:id="expressions-bean-references"> 914 <title>Bean references</title> 915 <para>If the evaluation context has been configured with a bean resolver it is possible to 916 lookup beans from an expression using the (@) symbol. 917 </para> 918 <programlisting language="java">ExpressionParser parser = new SpelExpressionParser(); 919StandardEvaluationContext context = new StandardEvaluationContext(); 920context.setBeanResolver(new MyBeanResolver()); 921 922// This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation 923Object bean = parser.parseExpression("@foo").getValue(context);</programlisting> 924 </section> 925 926 <section xml:id="expressions-operator-ternary"> 927 <title>Ternary Operator (If-Then-Else)</title> 928 929 <para>You can use the ternary operator for performing if-then-else 930 conditional logic inside the expression. A minimal example is:</para> 931 932 <programlisting language="java">String falseString = 933 parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class);</programlisting> 934 935 <para>In this case, the boolean false results in returning the string 936 value 'falseExp'. A more realistic example is shown below.</para> 937 938 <programlisting language="java">parser.parseExpression("Name").setValue(societyContext, "IEEE"); 939societyContext.setVariable("queryName", "Nikola Tesla"); 940 941expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + 942 "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"; 943 944String queryResultString = 945 parser.parseExpression(expression).getValue(societyContext, String.class); 946// queryResultString = "Nikola Tesla is a member of the IEEE Society"</programlisting> 947 948 <para>Also see the next section on the Elvis operator for an even 949 shorter syntax for the ternary operator.</para> 950 </section> 951 952 <section xml:id="expressions-operator-elvis"> 953 <title>The Elvis Operator</title> 954 955 <para>The Elvis operator is a shortening of the ternary operator syntax 956 and is used in the <link xl:href="http://groovy.codehaus.org/Operators#Operators-ElvisOperator(%3F%3A)">Groovy</link> 957 language. With the ternary operator syntax you usually have to repeat a 958 variable twice, for example:</para> 959 960 <programlisting>String name = "Elvis Presley"; 961String displayName = name != null ? name : "Unknown";</programlisting> 962 963 <para>Instead you can use the Elvis operator, named for the resemblance 964 to Elvis' hair style.</para> 965 966 <programlisting language="java">ExpressionParser parser = new SpelExpressionParser(); 967 968String name = parser.parseExpression("null?:'Unknown'").getValue(String.class); 969 970System.out.println(name); // 'Unknown' 971 972</programlisting> 973 974 <para>Here is a more complex example.</para> 975 976 <programlisting language="java">ExpressionParser parser = new SpelExpressionParser(); 977 978Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); 979StandardEvaluationContext context = new StandardEvaluationContext(tesla); 980 981String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class); 982 983System.out.println(name); // Mike Tesla 984 985tesla.setName(null); 986 987name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class); 988 989System.out.println(name); // Elvis Presley</programlisting> 990 </section> 991 992 <section xml:id="expressions-operator-safe-navigation"> 993 <title>Safe Navigation operator</title> 994 995 <para>The Safe Navigation operator is used to avoid a 996 <literal>NullPointerException</literal> and comes from the <link 997 xl:href="http://groovy.codehaus.org/Operators#Operators-SafeNavigationOperator(%3F.)">Groovy</link> 998 language. Typically when you have a reference to an object you might 999 need to verify that it is not null before accessing methods or 1000 properties of the object. To avoid this, the safe navigation operator 1001 will simply return null instead of throwing an exception.</para> 1002 1003 <programlisting language="java">ExpressionParser parser = new SpelExpressionParser(); 1004 1005Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); 1006tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); 1007 1008StandardEvaluationContext context = new StandardEvaluationContext(tesla); 1009 1010String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class); 1011System.out.println(city); // Smiljan 1012 1013tesla.setPlaceOfBirth(null); 1014 1015city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class); 1016 1017System.out.println(city); // null - does not throw NullPointerException!!!</programlisting> 1018 <note> 1019 <para>The Elvis operator can be used to apply default values in 1020 expressions, e.g. in an <interfacename>@Value</interfacename> expression:</para> 1021 1022 <programlisting>@Value("#{systemProperties['pop3.port'] ?: 25}")</programlisting> 1023 1024 <para>This will inject a system property <code>pop3.port</code> if it 1025 is defined or 25 if not.</para> 1026 </note> 1027 </section> 1028 1029 <section xml:id="expressions-collection-selection"> 1030 <title>Collection Selection</title> 1031 1032 <para>Selection is a powerful expression language feature that allows you 1033 to transform some source collection into another by selecting from its 1034 entries.</para> 1035 1036 <para>Selection uses the syntax 1037 <literal>?[selectionExpression]</literal>. This will filter the 1038 collection and return a new collection containing a subset of the 1039 original elements. For example, selection would allow us to easily get a 1040 list of Serbian inventors:</para> 1041 1042 <programlisting language="java">List<Inventor> list = (List<Inventor>) 1043 parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext);</programlisting> 1044 1045 <para>Selection is possible upon both lists and maps. In the former case 1046 the selection criteria is evaluated against each individual list element 1047 whilst against a map the selection criteria is evaluated against each 1048 map entry (objects of the Java type <literal>Map.Entry</literal>). Map 1049 entries have their key and value accessible as properties for use in the 1050 selection.</para> 1051 1052 <para>This expression will return a new map consisting of those elements 1053 of the original map where the entry value is less than 27.</para> 1054 1055 <programlisting language="java">Map newMap = parser.parseExpression("map.?[value<27]").getValue();</programlisting> 1056 1057 <para>In addition to returning all the selected elements, it is possible 1058 to retrieve just the first or the last value. To obtain the first entry 1059 matching the selection the syntax is <literal>^[...]</literal> whilst to 1060 obtain the last matching selection the syntax is 1061 <literal>$[...]</literal>.</para> 1062 </section> 1063 1064 <section xml:id="expressions-collection-projection"> 1065 <title>Collection Projection</title> 1066 1067 <para>Projection allows a collection to drive the evaluation of a 1068 sub-expression and the result is a new collection. The syntax for 1069 projection is <literal>![projectionExpression]</literal>. Most easily 1070 understood by example, suppose we have a list of inventors but want the 1071 list of cities where they were born. Effectively we want to evaluate 1072 'placeOfBirth.city' for every entry in the inventor list. Using 1073 projection:</para> 1074 1075 <programlisting language="java">// returns [ 'Smiljan', 'Idvor' ] 1076List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");</programlisting> 1077 1078 <para>A map can also be used to drive projection and in this case the 1079 projection expression is evaluated against each entry in the map 1080 (represented as a Java <literal>Map.Entry</literal>). The result of a 1081 projection across a map is a list consisting of the evaluation of the 1082 projection expression against each map entry.</para> 1083 </section> 1084 1085 <section xml:id="expressions-templating"> 1086 <title>Expression templating</title> 1087 1088 <para>Expression templates allow a mixing of literal text with one or 1089 more evaluation blocks. Each evaluation block is delimited with prefix 1090 and suffix characters that you can define, a common choice is to use 1091 <literal>#{ }</literal> as the delimiters. For example,</para> 1092 1093 <programlisting language="java">String randomPhrase = 1094 parser.parseExpression("random number is #{T(java.lang.Math).random()}", 1095 new TemplateParserContext()).getValue(String.class); 1096 1097// evaluates to "random number is 0.7038186818312008"</programlisting> 1098 1099 <para>The string is evaluated by concatenating the literal text 'random 1100 number is ' with the result of evaluating the expression inside the #{ } 1101 delimiter, in this case the result of calling that random() method. The 1102 second argument to the method <literal>parseExpression()</literal> is of 1103 the type <interfacename>ParserContext</interfacename>. The 1104 <interfacename>ParserContext</interfacename> interface is used to 1105 influence how the expression is parsed in order to support the 1106 expression templating functionality. The definition of 1107 <classname>TemplateParserContext</classname> is shown below.</para> 1108 1109 <programlisting language="java">public class TemplateParserContext implements ParserContext { 1110 1111 public String getExpressionPrefix() { 1112 return "#{"; 1113 } 1114 1115 public String getExpressionSuffix() { 1116 return "}"; 1117 } 1118 1119 public boolean isTemplate() { 1120 return true; 1121 } 1122}</programlisting> 1123 </section> 1124 </section> 1125 1126 <section xml:id="expressions-example-classes"> 1127 <title>Classes used in the examples</title> 1128 1129 <para>Inventor.java</para> 1130 1131 <programlisting language="java">package org.spring.samples.spel.inventor; 1132 1133import java.util.Date; 1134import java.util.GregorianCalendar; 1135 1136public class Inventor { 1137 1138 private String name; 1139 private String nationality; 1140 private String[] inventions; 1141 private Date birthdate; 1142 private PlaceOfBirth placeOfBirth; 1143 1144 1145 public Inventor(String name, String nationality) 1146 { 1147 GregorianCalendar c= new GregorianCalendar(); 1148 this.name = name; 1149 this.nationality = nationality; 1150 this.birthdate = c.getTime(); 1151 } 1152 public Inventor(String name, Date birthdate, String nationality) { 1153 this.name = name; 1154 this.nationality = nationality; 1155 this.birthdate = birthdate; 1156 } 1157 1158 public Inventor() { 1159 } 1160 1161 public String getName() { 1162 return name; 1163 } 1164 public void setName(String name) { 1165 this.name = name; 1166 } 1167 public String getNationality() { 1168 return nationality; 1169 } 1170 public void setNationality(String nationality) { 1171 this.nationality = nationality; 1172 } 1173 public Date getBirthdate() { 1174 return birthdate; 1175 } 1176 public void setBirthdate(Date birthdate) { 1177 this.birthdate = birthdate; 1178 } 1179 public PlaceOfBirth getPlaceOfBirth() { 1180 return placeOfBirth; 1181 } 1182 public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) { 1183 this.placeOfBirth = placeOfBirth; 1184 } 1185 public void setInventions(String[] inventions) { 1186 this.inventions = inventions; 1187 } 1188 public String[] getInventions() { 1189 return inventions; 1190 } 1191} 1192</programlisting> 1193 1194 <para>PlaceOfBirth.java</para> 1195 1196 <programlisting language="java">package org.spring.samples.spel.inventor; 1197 1198public class PlaceOfBirth { 1199 1200 private String city; 1201 private String country; 1202 1203 public PlaceOfBirth(String city) { 1204 this.city=city; 1205 } 1206 public PlaceOfBirth(String city, String country) 1207 { 1208 this(city); 1209 this.country = country; 1210 } 1211 1212 1213 public String getCity() { 1214 return city; 1215 } 1216 public void setCity(String s) { 1217 this.city = s; 1218 } 1219 public String getCountry() { 1220 return country; 1221 } 1222 public void setCountry(String country) { 1223 this.country = country; 1224 } 1225 1226 1227 1228} 1229</programlisting> 1230 1231 <para>Society.java</para> 1232 1233 <programlisting language="java">package org.spring.samples.spel.inventor; 1234 1235import java.util.*; 1236 1237public class Society { 1238 1239 private String name; 1240 1241 public static String Advisors = "advisors"; 1242 public static String President = "president"; 1243 1244 private List<Inventor> members = new ArrayList<Inventor>(); 1245 private Map officers = new HashMap(); 1246 1247 public List getMembers() { 1248 return members; 1249 } 1250 1251 public Map getOfficers() { 1252 return officers; 1253 } 1254 1255 public String getName() { 1256 return name; 1257 } 1258 1259 public void setName(String name) { 1260 this.name = name; 1261 } 1262 1263 public boolean isMember(String name) 1264 { 1265 boolean found = false; 1266 for (Inventor inventor : members) { 1267 if (inventor.getName().equals(name)) 1268 { 1269 found = true; 1270 break; 1271 } 1272 } 1273 return found; 1274 } 1275 1276 1277} 1278</programlisting> 1279 </section> 1280</chapter> 1281