SpEL stands for Spring Expression Language. It is usually used for easy evaluation in XML or annotations, and can be used by writing a format like #{}.

Use in the Bean definition

The XML configuration

You can use SpEL to set property or constructor parameter values, as shown in the following example:

    <bean id="numberGuess" class="com.flydean.beans.NumberGuess">
        <property name="randomNumber" value="#{T(java.lang.math).random() * 100.0}"/>

        <! -- other properties -->
    </bean>
Copy the code

Spring comes with many predefined variables, such as SystemProperties, which you can reference directly like this:

    <bean id="taxCalculator" class="com.flydean.beans.TaxCalculator">
        <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>

        <! -- other properties -->
    </bean>
Copy the code

Similarly, you can refer to other bean properties by name, as shown in the following example:

    <bean id="shapeGuess" class="com.flydean.beans.NumberGuess">
        <property name="randomNumber" value="#{ numberGuess.randomNumber }"/>

        <! -- other properties -->
    </bean>
Copy the code

Annotation configuration

To specify a default value, place the @Value annotation on a field, method, method, or constructor parameter.

The following example sets the default value of a field variable:

public static class FieldValueTestBean

    @Value(" #{ systemProperties['user.region']}") private String defaultLocale; public void setDefaultLocale(String defaultLocale) { this.defaultLocale = defaultLocale; } public String getDefaultLocale() { return this.defaultLocale; }}Copy the code

The following example shows an example on a property setter method:

public static class PropertyValueTestBean

    private String defaultLocale;

    @Value("#{ systemProperties['user.region'] }")
    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale(a) {
        return this.defaultLocale; }}Copy the code

Autowired methods and constructors can also use @value annotations, as shown in the following example:

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;
    }

    // ...
}
Copy the code
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;
    }

    // ...
}
Copy the code

evaluation

Although SpEL is typically used in Spring XML and annotations, it can be used independently of Spring, requiring you to create your own bootstrap infrastructure classes, such as parsers. Most Spring users don’t need to deal with this infrastructure, just write expression strings to evaluate.

Supported functions

SpELl supports a wide variety of functions including:

  • Literal expression
  • Properties, arrays, lists, maps, and indexers
  • Inline List
  • The inline Map
  • Array
  • methods
  • Operators
  • type
  • Constructors
  • variable
  • function
  • Bean reference
  • Ternary operators (if-then-else)
  • elvis
  • Safe Navigation Operator

Here are some examples:

Literal expression

Supported text expression types include strings, values (int, real, HEX), Booleans, and NULL. Strings are separated by single quotation marks. To place the single quote itself in a string, use two single quote characters.

public class LiteralApp {
    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();

// evals to "Hello World"
        String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();

        double avogadrosNumber = (Double) parser.parseExpression("6.0221415 e+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(); }}Copy the code

Properties, Arrays, Lists, Maps, and Indexers

The Properties through “.” To access nested property values. As follows:

// evals to 1856
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);

String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
Copy the code

The first letter of an attribute name is allowed to be case insensitive. The contents of arrays and lists are obtained using square bracket notation, as shown in the following example

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// Inventions Array

// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
        context, tesla, String.class);

// Members List

// evaluates to "Nikola Tesla"
String name = parser.parseExpression("Members[0].Name").getValue(
        context, ieee, String.class);

// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
        context, ieee, String.class);
Copy the code

The contents of the map are obtained by specifying text key values in parentheses:

// 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");
Copy the code

Inline List

You can express lists directly in expressions:

// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1, 2, 3, 4}").getValue(context);

List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
Copy the code

Inline Map

You can also use key:value notation to represent mappings directly in expressions. The following example shows how to do this:

// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);

Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);

Copy the code

Constructing an array

Arrays can be built using familiar Java syntax, optionally providing initializers to populate arrays at build time. The following example shows how to do this:

int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);

// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("New int [] {1, 2, 3}").getValue(context);

// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
Copy the code

methods

Methods can be invoked using typical Java programming syntax. You can also call methods on text. Variable parameters are also supported. The following example shows how to call a method:

// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);

// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean.class);
Copy the code

type

You can use the special T operator to specify instances of java.lang.class (type). Static methods can also be called using this operator. StandardEvaluationContext using TypeLocator to lookup type, StandardTypeLocator (can replace) is to understand the Java lang package on the basis of the building. This means that T () references to types in java.lang do not need to be fully qualified, but all other type references must be qualified. The following example shows how to use the T operator:

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);
Copy the code

The constructor

Constructors can be called using the new operator. Fully qualified class names should be used for all types except primitive types (int, float, and so on) and strings. The following example shows how to call a constructor using the new operator:

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);
Copy the code

variable

Variables in expressions can be referenced using the #variableName syntax. Variables are set using the setVariable method on the EvaluationContext implementation. The following example shows how to use variables:

Inventor tesla = new Inventor("Nikola Tesla"."Serbian");

EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName"."Mike Tesla");

parser.parseExpression("Name = #newName").getValue(context, tesla);
System.out.println(tesla.getName())  // "Mike Tesla"
Copy the code

# # this and root

#this is always defined and refers to the current evaluation object. The root variable is always defined and references the root context object. #root always refers to the root, although #this may change as the expression’s components are evaluated. The following example shows how to use the #this and #root variables:

// 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();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
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);
Copy the code

function

You can extend SPEL by registering user-defined functions that can be called in expression strings. This function is registered with EvaluationContext. The following example shows how to register a user-defined function:

public abstract class StringUtils {

    public static String reverseString(String input) {
        StringBuilder backwards = new StringBuilder(input.length());
        for (int i = 0; i < input.length(); i++)
            backwards.append(input.charAt(input.length() - 1 - i));
        }
        returnbackwards.toString(); }}Copy the code
ExpressionParser parser = new SpelExpressionParser();

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
        StringUtils.class.getDeclaredMethod("reverseString", String.class));

String helloWorldReversed = parser.parseExpression(
        "#reverseString('hello')").getValue(context, String.class);
Copy the code

Bean reference

If you have configured the evaluation context using the bean resolver, you can use the @ symbol to look up the bean from the expression. The following example shows how to do this:

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);
Copy the code

To access the factory bean itself, you should precede the bean name with an ampersand. The following example shows how to do this:

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);
Copy the code

If-Then-Else

You can use ternary operators to perform if-then-else conditional logic in expressions. The following list shows a minimal example:

String falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String.class);
Copy the code

Elvis

The ELVIS operator is an abbreviation for the triple operator syntax used in the Groovy language. For ternary operator syntax, the variable must normally be repeated twice, as shown in the following example:

String name = "Elvis Presley"; String displayName = (name ! =null ? name : "Unknown");
Copy the code

Instead, you can use the Elvis operator (named after the Elvis’s hairstyle). The following example shows how to use the Elvis operator:

ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("name? :'Unknown'").getValue(String.class);
System.out.println(name);  // 'Unknown'
Copy the code

Safe Navigation operator

The Safe Navigation operator, used to avoid NullPointerExceptions, comes from the Groovy language. Typically, when you refer to an object, you may need to verify that it is not empty before accessing the object’s methods or properties. To avoid this, the Safe Navigation operator returns a null value instead of throwing an exception. The following example shows how to use Safe Navigation:

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

Inventor tesla = new Inventor("Nikola Tesla"."Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));

String city = parser.parseExpression("PlaceOfBirth? .City").getValue(context, tesla, String.class);
System.out.println(city);  // Smiljan

tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth? .City").getValue(context, tesla, String.class);
System.out.println(city);  // null - does not throw NullPointerException!!!
Copy the code

Collection selection

Selection is a powerful expression language feature that allows you to transform a collection of sources into another collection by selecting from its entries. Selection uses the.? [selectionExpression]. It filters the collection and returns a new collection containing a subset of the original elements. For example, Selection makes it easy to get a list of Serbian inventors, as shown in the following example:

List<Inventor> list = (List<Inventor>) parser.parseExpression(
        "Members.? [Nationality == 'Serbian']").getValue(societyContext);
Copy the code

Selection is available on both lists and maps. For lists, selection criteria are evaluated against each individual list element. For maps, selection criteria are evaluated for each mapping Entry (Java type map.Entry). Each map entry has its keys and values, which can be accessed as properties for use in selection. The following expression returns a new map consisting of the elements of the original map, where the input value is less than 27:

Map newMap = parser.parseExpression("map.? [value<27]").getValue();
Copy the code

In addition to returning all selected elements, you can retrieve the first or last value. To get the first entry that matches the selection, the syntax is. . ^ [selectionExpression]. To get the last matched selection, the syntax is.$[SelectionExpression].

Set the projection

Projection allows collections to drive the evaluation of subexpressions, resulting in a new collection. The syntax for a projection is…! [projectionExpression]. For example, suppose we have a list of inventors, but want a list of the cities in which they were born. In fact, we want to evaluate placeofBirth.city for each entry in the inventor list. The following example uses a projection to do this:

// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.! [placeOfBirth.city]");
Copy the code

You can also use maps to drive projections, in which case the projection expression is evaluated against each Entry in the map (represented as Java map.entry). The result of a cross-map projection is a list containing the calculation of the projection expression for each map entry.

Expression templating

Expression templates allow text to be mixed with one or more blocks of computation. Each evaluation block is separated by prefix and suffix characters that you can define. A common choice is to use #{} as a delimiter, as shown in the following example:

String randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}".new TemplateParserContext()).getValue(String.class);

// evaluates to "random number is 0.7038186818312008"
Copy the code

The string is evaluated by concatenating the text “Random number is” with the result of evaluating the expression inside the #{} delimiter (in this case, the result of calling the random () method). The second argument to the parseExpression () method is of type parserContext. The ParserContext interface is used to influence how expressions are parsed to support expression templating. TemplateParserContext is defined as follows:

public class TemplateParserContext implements ParserContext {

    public String getExpressionPrefix(a) {
        return "# {";
    }

    public String getExpressionSuffix(a) {
        return "}";
    }

    public boolean isTemplate(a) {
        return true; }}Copy the code

Refer to SPEL for examples in this section

See flydean’s blog for more tutorials