Spring SpEL

2016/02/26

Quick note after learning Spring SpEL. Code Demo

Spring Expression Language (SpEL), like OGNL, is a powerful expression language that supports querying and manipulating an object graph at runtime.

To use SpEL, we need org.springframework.spring-expression jar.

Hello World

ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("('Hello' + 'World').concat(#end)");

// Create a context
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("end", "!"); // Set variable end with value !
System.out.println(expression.getValue(context)); // HelloWorld!

// Create another context
EvaluationContext anotherContext = new StandardEvaluationContext();
context.setVariable("end", "yoyo"); // // Set variable end with value yoyo
System.out.println(expression.getValue(anotherContext)); // HelloWorldyoyo

The interface EvaluationContext is used as a context for the expression. The out-of-the-box implementation, StandardEvaluationContext, uses reflection to manipulate the object.

Literal expression

The types of literal expressions supported are strings, dates, numeric values (int, real, and hex), boolean and null. Strings are delimited by single quotes. To put a single quote itself in a string use two single quote characters.

We firstly define a class to test:

public class SpELLiteral {

	private int count;
	private String message;
	private float frequency;
	private float capacity;
	private String name1;
	private String name2;
	private boolean enabled;

	// Setters and getters
}

In conf-spel.xml, we use #{expression} to indicate a SpEL. :

<bean id="spELLiteral" class="com.dong.demo.SpELLiteral">
	<property name="count" value="#{5}"/>
	<property name="message" value="The value is #{5}"/>
	<property name="frequency" value="#{89.7}"/>
	<property name="capacity" value="#{1e4}"/>
	<property name="name1" value="#{'Chuck'}"/>
	<property name="name2" value='#{"Chuck"}'/>
	<property name="enabled" value="#{false}"/>
</bean>

To test:

ApplicationContext ctx = new ClassPathXmlApplicationContext("conf-spel.xml");

SpELLiteral spelLiteral = ctx.getBean("spELLiteral", SpELLiteral.class);

System.out.println(spelLiteral.getCount());     // 5
System.out.println(spelLiteral.getMessage());   // The value is 5
System.out.println(spelLiteral.getFrequency()); // 89.7
System.out.println(spelLiteral.getCapacity());  // 10000.0
System.out.println(spelLiteral.getName1());     // Chuck
System.out.println(spelLiteral.getName2());     // Chuck
System.out.println(spelLiteral.isEnabled());    // false

Variable

Like above hello world example. We could use variable by setVariable() function of EvaluationContext which means context for expression. Variables can be referenced in the expression using the syntax #variableName.

ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("Name = #newName");
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("newName", "dongchuan"); // Set variable newName with value dongchuan
System.out.println(expression.getValue(context)); // Name = dongchuan

Xml-based Expression

**SpEL could reference the value of another bean by #{beanID} or #{@beanID}##. And as it’s an expression, we could add some other operations in it:

<!-- Creat a string with value ' World!'-->
<bean id="world" class="java.lang.String" >
	<constructor-arg value="#{' World!'}" />
</bean>

<bean id="hello1" class="java.lang.String" >
	<constructor-arg value="#{'Hello'}#{world}" />
</bean>
<bean id="hello2" class="java.lang.String" >
	<constructor-arg value="#{'Hello' + world}" />
	<!-- But the following nested expression not supported -->
	<!-- constructor-arg value="#{'Hello'#{world}}" /-->
</bean>
<bean id="hello3" class="java.lang.String" >
	<constructor-arg value="#{'Hello' + @world}" />
</bean>

Annotation-based Expression

The @Value annotation can be placed on fields, methods and method/constructor parameters to specify a default value. So we could use SpEL to reference other beans to set default value.

public class AnnoExpression {

	@Value("#{'Hello ' + world}")
	private String value;

	// We could also put @Value on setXxx() method. They have the same result
	// Setters and Getters

}

Configuration file conf-spel.xml:

<!-- To support annotation -->
<context:annotation-config/>

<!-- Creat a string with value ' World!'-->
<bean id="world" class="java.lang.String" >
	<constructor-arg value="#{' World!'}" />
</bean>

<!-- Value set by annotation -->
<bean id="helloBean1" class="com.dong.demo.AnnoExpression"/>
<!-- Override annotation value -->
<bean id="helloBean2" class="com.dong.demo.AnnoExpression">
	<property name="value" value="helloBean2"/>
</bean>

To test:

ApplicationContext ctx = new ClassPathXmlApplicationContext("conf-spel.xml");

AnnoExpression helloBean1 = ctx.getBean("helloBean1", AnnoExpression.class);
AnnoExpression helloBean2 = ctx.getBean("helloBean2", AnnoExpression.class);

System.out.println("helloBean1 : " + helloBean1.getValue()); // helloBean1 : Hello World!
System.out.println("helloBean2 : " + helloBean2.getValue()); // helloBean2 : helloBean2

Function or constant in expressions

To invoke static function/constant in expression, we must know its class firstly by T(packagePath). Then call its function T(packagePath).Staticfunction(). In fact, the T operator is used to tell SpEL to deal with String inside T{} as class type.

T() references to types within java.lang do not need to be fully qualified, but all other type references must be.

To invoke class function, it’s the same as java language. If we already have an instance in SpEL, for example #{'HelloWorld'}, here as a String instance ,we could call all the string class function directly like this #{'HelloWorld'.function()}

<bean id="spELClass" class="com.dong.demo.SpELClass">
	<!-- Invoke class function -->
    <property name="classFunction" value="#{'HelloWorld'.substring(2, 5)}"/>
	<!-- Invoke constant -->
    <property name="pi" value="#{T(java.lang.Math).PI}"/>
    <!-- Invoke static function -->
    <property name="randomNumber" value="#{T(java.lang.Math).random()}"/>
</bean>

Constructor in expressions

We could use new key word directly in SpEL, if class is not in java.lang, we need to specify the full package name:

<bean id="spELClass" class="com.dong.demo.SpELConstructor">
    <property name="testA" value="#{new String('HelloWorld').substring(2, 5)}"/>
    <property name="testB" value="#{new com.dong.demo.test('HelloWorld').doSomething()}"/>
</bean>

Mathematical operators

  • Addition operator can be used on both numbers and strings.
  • Subtraction, multiplication and division can be used only on numbers.
  • Other mathematical operators supported are modulus (%) and exponential power (^).
<bean id="counter" class="com.dong.demo.SpELCounter">
	<property name="total" value="#{100}"/>
	<property name="count" value="#{10}"/>
</bean>

<bean id="spELMath" class="com.dong.demo.SpELMath">
	<property name="addition" value="#{counter.total + 42}"/>
	<property name="multiplication" value="#{2 * T(java.lang.Math).PI * counter.total}"/>
	<property name="division" value="#{counter.total / counter.count}"/>
	<property name="complementation" value="#{counter.total % counter.count}"/>
	<property name="involution" value="#{T(java.lang.Math).PI * counter.total ^ 2}"/>
</bean>

Relational operators

There are two formats symbolic and textual. Textual format is suggested because it could avoid problems where the symbols used have special meaning for the document type in which the expression is embedded (eg. in XML).

  • lt (<)
  • gt (>)
  • le (<=)
  • ge (>=)
  • eq (==)
  • ne (!=)

Logic operators

  • or
  • and
  • not (!)
String expression = "isMember('Tom') and !isMember('Jean')";

Elvis Operator

The Elvis operator is a shortening of the ternary operator syntax. It could avoid repeating a variable twice.

name != null ? name : "someValue"

Same as:

name ? : "someValue"

Safe Navigation operator

Sometimes, we need to verify that an object is not null before accessing methods or properties of the object. The safe navigation operator will simply return null instead of throwing an exception.

parser.parseExpression("#person?.name").getValue(); // Return null
parser.parseExpression("#person.name").getValue(); // Throw NullPointerException

Collection Selection

collection.?[condition_expression] will filter collection and return a new collection containing a subset of the original elements match conditions.

The following example selects from list cities all the cities which has more than 100000 populations:

<!-- Create a list of beans -->
<util:list id="cities">
    <bean class="com.dong.demo.SpELCity" p:name="Chicago" p:state="IL" p:population="2853114"/>
    <bean class="com.dong.demo.SpELCity" p:name="Atlanta" p:state="GA" p:population="537958"/>
    <bean class="com.dong.demo.SpELCity" p:name="Dallas" p:state="TX" p:population="1279910"/>
    <bean class="com.dong.demo.SpELCity" p:name="Houston" p:state="TX" p:population="2242193"/>
    <bean class="com.dong.demo.SpELCity" p:name="Odessa" p:state="TX" p:population="90943"/>
    <bean class="com.dong.demo.SpELCity" p:name="El Paso" p:state="TX" p:population="613190"/>
    <bean class="com.dong.demo.SpELCity" p:name="Jal" p:state="NM" p:population="1996"/>
    <bean class="com.dong.demo.SpELCity" p:name="Las Cruces" p:state="NM" p:population="91865">
</util:list>

<bean id="spELCityList" class="com.dong.demo.SpELCityList">
	<property name="bigCities" value="#{cities.?[population gt 100000]}"/>
</bean>

Collection Projection

collection.!condition_expression] will create a new collection containing a subset which is the value of condition_expression.

The following example creates a new collection. Each of its item is a string combination of name and state from list cities:

<!-- Same list as above example -->

<bean id="spELCityList" class="com.dong.demo.SpELCityList">
	<property name="cityNames2" value="#{cities.![name + ', ' + state]}"/>
</bean>

Expression templating

Expression templates allow a mixing of literal text with one or more evaluation blocks. Each evaluation block is delimited with #{ }.

// Persion is a class has two properties name and height
Person p1 = new Persion("DONG", 180);
Person p1 = new Persion("Chuan", 190);

Expression expr = parser.parseExpression('my name is #{name}, my height is #{height}', new TemplateParserContext());

System.out.println(expr.getValue(p1));
System.out.println(expr.getValue(p2));

Ref

Post Directory