In Spring, the Spring Expression Language (SpEL) provides a powerful syntax for querying and manipulating objects at runtime.
SpEL enables developers to perform operations such as checking states, querying object graphs, and causing side effects declaratively, using expressions that are written in a special syntax.
While these operations are traditionally expressed in code, there are situations where it’s more appropriate to express them declaratively, such as in annotations or XML files. With SpEL, developers can set statements declaratively, which are then executed at runtime.
SpEL is a core part of the Spring Framework, and it offers a variety of features, such as the ability to reference properties, call methods, and perform arithmetic operations. SpEL also supports conditional expressions and regex, making it a versatile tool for developers.
The expressions that are written with SpEL are called SpEL expressions. To gain a better understanding of SpEL, it’s useful to explore some examples and learn the basic syntax.
ExpressionParser
An ExpressionParser can be used to evaluate SpEL expressions. An object can be created via SpelExpressionParser via the constructor:
ExpressionParser parser = new SpelExpressionParser();
The ExpressionParser type has method parseExpression(String) that evaluates a SpEL. Here are a few examples:
Expression exp = parser.parseExpression( "1+2*3 >= 7 and false" );
System.out.println( exp.getValue() ); // false
As you can see, you can evaluate arithmetic expressions, including the precedence, of course, so that 1 + 2 * 3 is 7. Comparison operators exist as well. For better readability, some operators have words such as or, and, or not. The overall result of the shown expression is false.
exp = parser.parseExpression( "'Hello '.concat('World').toUpperCase()" );
System.out.println( exp.getValue() ); // HELLO WORLD
Strings are written in Java with double quotes, and because SpEL can contain strings, single quotes are used to specify characters or strings. Regular methods can be called on the String object, for example, concat(…) or toUpperCase().
exp = parser.parseExpression( "new java.awt.Point(10,20).location.x" );
System.out.println( exp.getValue() ); // 10.0
In a SpEL expression, objects can be built with the keyword new. Property accesses are possible, that is, location and x aren’t variables, but the SpEL engine automatically calls the getters, that is, first getLocation() on the Point and then getX(). There is a public variable x on the Point object, but the getX() method is still called.
exp = parser.parseExpression( "{1,2,3,4}.get(0) == 1 ? 'one' : 'else'" );
System.out.println( exp.getValue() ); // one
Anything enclosed in curly braces is a list and translates to a java.util.List. List has a get(int) method; that is, the expression accesses the first element, and that is 1. In SpEL, there is the conditional operator as well.
exp = parser.parseExpression( "'12345' matches '\\d+'" );
System.out.println( exp.getValue() ); // true
SpEL has a special keyword matches that we can use to make simple and straightforward regex queries. Again, we have a string 12345, and the question is whether this matches any number of digits. It does!
exp = parser.parseExpression( "T(java.lang.Math).random()" );
System.out.println( exp.getValue() );
The notation seems strange in part because it’s written with T(). This is used for calling static methods. A fully qualified class name is placed in the round brackets:
exp =parser.parseExpression("T(java.lang.Runtime).getRuntime().exec('cmd')");
System.out.println( exp.getValue() );
// Process[pid=12345, exitValue="not exited"]
SpEL can be dangerous in principle, and developers must be careful not to open the door to attackers. In the output, you can see that the process was validly started, which means there could be a program running that could ruin our computer. However, certain precautions are taken by the expression parser based on the configuration.
SpEL in the Spring (Boot) API
Spring uses SpEL in many places; as examples, here follows a passage from Spring Boot and two passages from the Spring Framework itself.
@ConditionalOnExpression
At @ConditionalOnExpression, we can register a managed bean depending on a condition. The condition is specified as an annotation attribute. The declaration of the type looks like this:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnExpressionCondition.class)
public @interface ConditionalOnExpression {
/**
* The SpEL expression to evaluate. Expression should return
* {@code true} if the condition passes or {@code false} if it fails.
* @return the SpEL expression
*/
String value() default "true";
}
The Javadoc makes it clear that a new component is included in the context if a SpEL expression returns true, and not if it returns false.
Let’s connect this to a concrete example:
@Component
@ConditionalOnExpression( "1+2=3" )
// @ConditionalOnExpression(
// "not (systemProperties['user.home'] ?:'c:/').isEmpty()"
// )
// @ConditionalOnExpression( "@spEL != null" )
class Renderer { }
@SpringBootApplication
public class SpEL {
public static void main( String[] args ) {
ApplicationContext ctx = SpringApplication.run( SpEL.class, args );
System.out.println( ctx.containsBean( "renderer" ) ); // true
}
}
First, the main program SpEL starts the container normally; the class SpEL is the main configuration. The program asks with containsBean(…) if there is a bean with the name “renderer”, and everything above leads to a bean with the name “renderer”, so the outputs will always be “true”.
@ConditionalOnExpression( "1+2=3" )
The first test is simple: Is it true that 1 + 2 = 3? Of course! So, the condition is true, and consequently the bean renderer is built.
@ConditionalOnExpression(
"not (systemProperties['user.home'] ?:'c:/').isEmpty()"
)
The second SpEL expression asks whether the system properties have an assignment for user.home, and if so, the result is the assignment of user.home. Otherwise, the result is the string c:. The test is done by the Elvis operator ? :. Many programming languages have this operator, but Java doesn’t. The Elvis operator is a short form of the condition operator, which expresses that if the expression systemProperties[ 'user.home'] isn’t null, it’s the result; otherwise, the alternative is c:. Any result will be nonempty, so isEmpty() will return false; that turns not around, so it comes out true again in the end.
@ConditionalOnExpression( "@spEL != null" )
The third example shows how to fall back on Spring-managed beans, namely with the @ sign. The SpEL expression asks if a component named “spEL” is non-null. This is true because the class is called SpEL and gets the automatically lowercase bean name spEL. Basically, you could have called methods on the Spring-managed bean again.
@Cacheable
Another example using the @Cacheable annotation allows for caching method returns. This means that if a method is called again with the same arguments, the cached value can be returned instead of recalculating it.
With the help of SpEL, various aspects can be controlled declaratively. For example, caching should only occur when certain conditions apply. If you look at the Javadoc, you can easily see the annotation attributes that can be assigned to a SpEL expression.
Using these declarative SpEL expressions, we can refine queries because where else would the conditions be? We would have to program out the cache rules otherwise. Using declarative expressions on the annotations is elegant.
@Scheduled
For a third example, take a look at the annotation @Scheduled. Scheduling is about Spring being able to call methods on a regular basis. Now, with @Scheduled, there are two ways to specify values: once via a long and the other via a SpEL string.
That should be enough about SpEL for now. We’ll come back to it in a few places, but in everyday life, you won’t find SpEL that often. In addition, you have to keep in mind that such expressions are parsed only at runtime, and because they are strings, we have type safety issues, so when refactoring, we must not forget to adjust these strings. Users of IntelliJ Ultimate Edition enjoy the advantage that the IDE looks into SpEL expressions and detects certain errors—this is definitely a big bonus.
Editor’s note: This post has been adapted from a section of the book Spring Boot 3 and Spring Framework 6 by Christian Ullenboom.
Comments