Tuesday, 13 December 2016

Run time injection using Spring Expression Language(SpEL)

Spring Expression Language (SpEL) is available from Spring 3, it is a powerful expression language and provides an option for runtime injection rather than hardcoding values. Using SpEL you can inject values into the properties of a bean or constructor arguments that are evaluated at runtime.

Feature overview

The expression language supports the following functionality

  • Literal expressions
  • Boolean, relational and mathematical operators
  • Regular expressions
  • Accessing properties, arrays, lists, maps
  • Bean references
  • Calling constructors
  • Method invocation
  • Ternary operator

How to write expressions

Spring expression language can be used with XML based configuration or with annotation based configuration to inject values at runtime. You can also write your own code using ExpressionParser interface for parsing an expression.

SpEL expressions are written using the form #{Expression String}. As an example if you have to assign a literal using SpEL in XML configuration it can be done this way -

<bean id="employeeAddress" class="org.netjs.exp.Spring_Example.EmployeeAddress">
    <property name="pinCode" value = "#{'302001'}" />
</bean>

Of course providing a literal expression this way doesn’t offer much advantage, this example just shows the syntax of SpEL.

Bean reference, setting property and Method invocation with SpEL

Let us see one example of bean reference and method invocation with Spring expression language. There are two classes Employee and Address, in Employee class Address class object is referenced.

Address Class

public class Address {
 private String number;
 private String street;
 private String city;
 private String state;
 private String pinCode;
 
 public String getNumber() {
  return number;
 }
 public void setNumber(String number) {
  this.number = number;
 }
 public String getStreet() {
  return street;
 }
 public void setStreet(String street) {
  this.street = street;
 }
 public String getCity() {
  return city;
 }
 public void setCity(String city) {
  this.city = city;
 }
 public String getState() {
  return state;
 }
 public void setState(String state) {
  this.state = state;
 }
 public String getPinCode() {
  return pinCode;
 }
 
 public void setPinCode(String pinCode) {
  this.pinCode = pinCode;
 }
 
 public String getAddress(String empName){
  return empName + " works at " + number + ", " + street + ", " + city + ", " + state + ", " + pinCode;
 }
}

Employee class

public class Employee {
 private long empId;
 private String empName;
 private Address officeAddress;
 private String officeLocation;
 private String employeeInfo;
 
 public String getEmpName() {
  return empName;
 }
 public void setEmpName(String empName) {
  this.empName = empName;
 }
 
 public long getEmpId() {
  return empId;
 }
 public void setEmpId(long empId) {
  this.empId = empId;
 }
 public Address getOfficeAddress() {
  return officeAddress;
 }
 public void setOfficeAddress(Address officeAddress) {
  this.officeAddress = officeAddress;
 }
 public String getOfficeLocation() {
  return officeLocation;
 }
 public void setOfficeLocation(String officeLocation) {
  this.officeLocation = officeLocation;
 }
 public String getEmployeeInfo() {
  return employeeInfo;
 }
 public void setEmployeeInfo(String employeeInfo) {
  this.employeeInfo = employeeInfo;
 }
 
}

XML Configuration

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    
    
    <bean id="officeAddress" class="org.netjs.prog.Address">
        <property name="number" value = "101" />
        <property name="street" value = "#{'M I Road'}" />
        <property name="city" value = "Jaipur" />
        <property name="state" value = "Rajasthan" />
        <property name="pinCode" value = "#{'302001'}" />
    </bean>
    
    <bean id="employee" class="org.netjs.prog.Employee">
        <property name="empId" value = "1001" />
        <property name="empName" value = "Ram" />
        <!--  Bean reference through SpEL -->
        <property name="officeAddress" value = "#{officeAddress}" />
        <property name="officeLocation" value = "#{officeAddress.city}" />
        <!--  Method invocation through SpEL -->
        <property name="employeeInfo" value = "#{officeAddress.getAddress('Ram')}" />
    </bean>

</beans>

Here you can note that in the officeAddress Bean properties are given values using literal expressions.
In the employee bean officeAddress property is given address reference using SpEL. For property officeLocation it refers to the property city of officeAddress bean using SpEL. For property employeeInfo method invocation is used and it is calling getAddress method of class Address, again using SpEL.

Test class

You can run this code using the following code -

import org.netjs.prog.Employee;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {

 public static void main(String[] args) {
  ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("appcontext.xml");
  Employee emp = (Employee)context.getBean("employee");
  System.out.println("Office address " + emp.getOfficeAddress().getNumber());
  System.out.println("Location of office " + emp.getOfficeLocation());
  System.out.println("Employee info " + emp.getEmployeeInfo());
 }

}

Output

Office address 101
Location of office Jaipur
Employee info Ram works at 101, M I Road, Jaipur, Rajasthan, 302001

Using Annotation

You can write the same code using annotations. The @Component annotation for registering the bean and @Value for setting values into bean properties. If you want to retain the same functionality as in the above program using XML configuration then the class will look like -

Employee class

@Component("employee")
public class Employee {
 @Value("1001")
 private long empId;
 @Value("Ram")
 private String empName;
 @Value("#{officeAddress}")
 private Address officeAddress;
 @Value("#{officeAddress.city}")
 private String officeLocation;
 @Value("#{officeAddress.getAddress('Ram')}")
 private String employeeInfo;
 
 public String getEmpName() {
  return empName;
 }
 public void setEmpName(String empName) {
  this.empName = empName;
 }
 
 public long getEmpId() {
  return empId;
 }
 public void setEmpId(long empId) {
  this.empId = empId;
 }
 public Address getOfficeAddress() {
  return officeAddress;
 }
 public void setOfficeAddress(Address officeAddress) {
  this.officeAddress = officeAddress;
 }
 public String getOfficeLocation() {
  return officeLocation;
 }
 public void setOfficeLocation(String officeLocation) {
  this.officeLocation = officeLocation;
 }
 public String getEmployeeInfo() {
  return employeeInfo;
 }
 public void setEmployeeInfo(String employeeInfo) {
  this.employeeInfo = employeeInfo;
 }
 

}

Address class

@Component("officeAddress")
public class Address {
 @Value("101")
 private String number;
 @Value("#{'M I Road'}")
 private String street;
 @Value("Jaipur")
 private String city;
 @Value("Rajasthan")
 private String state;
 @Value("#{'302001'}")
 private String pinCode;
 
 public String getNumber() {
  return number;
 }
 public void setNumber(String number) {
  this.number = number;
 }
 public String getStreet() {
  return street;
 }
 public void setStreet(String street) {
  this.street = street;
 }
 public String getCity() {
  return city;
 }
 public void setCity(String city) {
  this.city = city;
 }
 public String getState() {
  return state;
 }
 public void setState(String state) {
  this.state = state;
 }
 public String getPinCode() {
  return pinCode;
 }
 
 public void setPinCode(String pinCode) {
  this.pinCode = pinCode;
 }
 
 public String getAddress(String empName){
  return empName + " works at " + number + ", " + street + ", " + city + ", " + state + ", " + pinCode;
 }
}

In that case your XML configuration doesn’t need to provide bean definitions as the beans were registered automatically but you do need to provide the base package which Spring needs to scan in order to register the classes.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    
    <context:component-scan base-package="org.netjs.prog" />

</beans>

Test Class

You can use the same code to run this code -

import org.netjs.prog.Employee;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {

 public static void main(String[] args) {
  ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("appcontext.xml");
  Employee emp = (Employee)context.getBean("employee");
  System.out.println("Office address " + emp.getOfficeAddress().getNumber());
  System.out.println("Location of office " + emp.getOfficeLocation());
  System.out.println("Employee info " + emp.getEmployeeInfo());
 }

}

Output

Office address 101
Location of office Jaipur
Employee info Ram works at 101, M I Road, Jaipur, Rajasthan, 302001

Using Type safe operator (?.)

In the above example in the employee bean, employeeInfo property uses the method invocation expression.

<property name="employeeInfo" value = "#{officeAddress.getAddress('Ram')}" />

Let’s say there is a requirement that this value should be in upper case so you’ll write it like this -

<property name="employeeInfo" value = "#{officeAddress.getAddres('Ram').toUpperCase()}" />

Here one problem is, what if getAddress() method returns null? In that case you will get an error -

Method call: Attempted to call method toUpperCase() on null context object

To avoid that you can use type-safe operator .?

<property name="employeeInfo" value = "#{officeAddress.getAddress('Ram')?.toUpperCase()}" />

Using type-safe operator ensures that whatever is on the left side of the operator doesn’t return null before calling the thing on the right. If null is returned from the left side type-safe operator will halt the execution there itself and return null.

SpEL Operators

SpEL supports many operators that can be used in forming the expression.

Relational operators

The relational operators; equal, not equal, less than, less than or equal, greater than, and greater than or equal are supported using standard operator notation. In addition to standard relational operators SpEL supports the instanceof and regular expression based matches operator. Each symbolic operator can also be specified as a purely alphabetic equivalent. This avoids problems where the symbols used have special meaning for the document type in which the expression is embedded (eg. an XML document). The textual equivalents are shown here: lt (<), gt (>), le (#), ge (>=), eq (==), ne (!=), div (/), mod (%), not (!). These are case insensitive.

Logical operators

The logical operators that are supported are and, or, and not.

Mathematical operators

The 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 (^).

Conditional Operators

?: (ternary), ?: (Elvis)

Let’s see an example using some of these operators, here we have one class OperatorDemo with int and String properties and another class OperatorTest where some of the operators are used.

OperatorDemo class

public class OperatorDemo {
 private int num1;
 private int num2;
 private int num3;
 private String name;
 private String city;
 public int getNum1() {
  return num1;
 }
 public void setNum1(int num1) {
  this.num1 = num1;
 }
 public int getNum2() {
  return num2;
 }
 public void setNum2(int num2) {
  this.num2 = num2;
 }
 public int getNum3() {
  return num3;
 }
 public void setNum3(int num3) {
  this.num3 = num3;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public String getCity() {
  return city;
 }
 public void setCity(String city) {
  this.city = city;
 }

}

OperatorTest Class

public class OperatorTest {
 private boolean lessThanCheck;
 private int sum;
 private String info;
 private boolean andCheck;
 private boolean orCheck;
 public boolean isLessThanCheck() {
  return lessThanCheck;
 }
 public void setLessThanCheck(boolean lessThanCheck) {
  this.lessThanCheck = lessThanCheck;
 }
 public int getSum() {
  return sum;
 }
 public void setSum(int sum) {
  this.sum = sum;
 }
 public String getInfo() {
  return info;
 }
 public void setInfo(String info) {
  this.info = info;
 }
 public boolean isAndCheck() {
  return andCheck;
 }
 public void setAndCheck(boolean andCheck) {
  this.andCheck = andCheck;
 }
 public boolean isOrCheck() {
  return orCheck;
 }
 public void setOrCheck(boolean orCheck) {
  this.orCheck = orCheck;
 }
 
}

XML Configuration

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!-- <context:component-scan base-package="org.netjs.prog" /> -->
    
    <bean id="opDemoBean" class="org.netjs.prog.OperatorDemo">
        <property name="num1" value = "10" />
        <property name="num2" value = "20" />
        <property name="num3" value = "30" />
        <property name="name" value = "Ram" />
        <property name="city" value = "Agra" />
    </bean>
    
    <bean id="opTestBean" class="org.netjs.prog.OperatorTest">
        <property name="lessThanCheck" value = "#{opDemoBean.num1 lt opDemoBean.num2}" />
        <property name="sum" value = "#{opDemoBean.num2 + opDemoBean.num3}" />
        <property name="info" value = "#{opDemoBean.name + ' from ' +  opDemoBean.city}" />
        <property name="andCheck" value = "#{opDemoBean.num1 == 10 and opDemoBean.num2 == 20}" />
        
        
        <property name="orCheck" value = "#{opDemoBean.num1 lt 10 or opDemoBean.num3 == 20}" />
    </bean>

</beans>

In order to test this code you can use the following code -

import org.netjs.prog.Employee;
import org.netjs.prog.OperatorTest;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {

 public static void main(String[] args) {
  ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("appcontext.xml");
  OperatorTest opTest = (OperatorTest)context.getBean("opTestBean");
  System.out.println("num1 is Less than num 2 " + opTest.isLessThanCheck());
  System.out.println("Info " + opTest.getInfo());
  System.out.println("Sum " + opTest.getSum());
  System.out.println("And check " + opTest.isAndCheck());
  System.out.println("Or check " + opTest.isOrCheck());
 }

}

Output

num1 is Less than num 2 true
Info Ram from Agra
Sum 50
And check true
Or check false

Using Ternary and Elvis operator

You can also use ternary operator as an expression. Suppose you want to assign value to num property based on logic that if num1 is less than num3 then num = 10 otherwise num = 30.

<property name="num" value = "#{opDemoBean.num1 lt opDemoBean.num3 ? 10 : 30 }" />

There is also a short form of ternary operator known as Elvis operator. When using ternary operator many a times we have to check for null. With the ternary operator syntax you usually have to repeat a variable twice, for example:

String info = name != null ? name : "Unknown";

Using Elvis operator you can write it like this -

<property name="info" value = "#{opDemoBean.name ?: 'Unknown'}" />

Using Types expression

If you are using static method in SpEL then use the T() operator.

As example if you want to assign some random number to property you can do it like this -

<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

Using regular expression with SpEL

SpEL has a matches operator to be used with regular expression. The matches operator returns true if regular expression matches the given value otherwise false.

As example

#{person.email matches '[a-zA-Z0-9._]+@[a-zA-Z0-9]+\\.com'}

Using SpEL with Collectione like map and list

CollectionDemo class

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CollectionDemo {
    private Map<String, String> map;
    private List<String> list;
    public CollectionDemo() {
        map = new HashMap<String, String>();
        map.put("KEY1", "Value 1");
        map.put("KEY2", "Value 2");
        map.put("KEY3", "Value 3");

        list = new ArrayList<String>();
        list.add("List Value 1");
        list.add("List Value 2");
        list.add("List Value 3");

    }
    public Map<String, String> getMap() {
        return map;
    }
    public void setMap(Map<String, String> map) {
        this.map = map;
    }
    public List<String> getList() {
        return list;
    }
    public void setList(List<String> list) {
        this.list = list;
    }   
}

CollectionTest Class

public class CollectionTest {
 private String mapValue;
 private String listValue;
 public String getMapValue() {
  return mapValue;
 }
 public void setMapValue(String mapValue) {
  this.mapValue = mapValue;
 }
 public String getListValue() {
  return listValue;
 }
 public void setListValue(String listValue) {
  this.listValue = listValue;
 } 
}

XML Configuration

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!-- <context:component-scan base-package="org.netjs.prog" /> -->
    
    <bean id="collDemoBean" class="org.netjs.prog.CollectionDemo">
        
    </bean>
    
    <bean id="collTestBean" class="org.netjs.prog.CollectionTest">
        <property name="mapValue" value = "#{collDemoBean.map['KEY2']}" />
        <property name="listValue" value = "#{collDemoBean.list[0]}" />
        
    </bean>

</beans>

To test it -

import org.netjs.prog.CollectionTest;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("appcontext.xml");
        CollectionTest collTest = (CollectionTest)context.getBean("collTestBean");
        System.out.println("Map value " + collTest.getMapValue());
        System.out.println("List Value  " + collTest.getListValue());
        context.close();
    }

}

Output

Map value Value 2
List Value  List Value 1

That's all for this topic Run time injection using Spring Expression Language(SpEL). If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. What is Dependency Injection in Spring
  2. Dependency Injection using factory-method in Spring
  3. Wiring collections in Spring
  4. Spring example program using JavaConfig and Annotations
  5. Spring example program using automatic configuration
  6. How to inject prototype scoped bean in singleton bean

You may also like -

>>>Go to Spring tutorial page

No comments:

Post a Comment