Spel表达式学习

简介

Spring Expression Language (SpEL) 是强大的表达式语言,支持查询、操作运行时对象图,以及解析逻辑、算术表达式。SpEL可以独立使用,无论是否使用Spring框架。还有一些其他的表达式语言,比如OGNL,可用增强Arthas问题排查。

示例

解析计算

1
2
3
4
5
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); 
String message = (String) exp.getValue();

// Hello World

基本所有可能用到的SpEL类和接口都在org.springframework.expression和其子包下面,比如spel.support
ExpressionParser接口用以解析表达式字符串,在上述例子中,表达式串是由单引号'括起来的一个字符串。Expression接口用来计算先前已解析的表达式字符串。在此过程可能抛两种异常,ParseException
EvaluationException,分别在parser.parseExpression的时候和exp.getValue()的时候。

SpEL还可支持调用方法,访问属性,调用构造器等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Hello World!
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();

// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); (1)
int length = (Integer) exp.getValue();

Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); 
String message = exp.getValue(String.class);

SpEL 更常见的用法是提供针对特定对象实例(称为根对象)进行j表达式字符串。下面是一个调用构造方法的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("name"); // Parse name as an expression
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);

应用

Bean Definitions

可以通过XML和注解两种方式来进行使用,语法都是#{ <expression string> }

XML

1
2
3
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
  <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
</bean>

应用程序上下文中的所有Bean都可以作为预定义的变量,以其共同的Bean名称来使用。这包括标准的上下文Bean,如environment(类型为org.springframework.core.env.Environment),以及用于访问运行时环境的systemPropertiessystemEnvironment(类型为Map<String, Object>)。注意:不必在这里用#符号给预定义的变量加前缀。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
    <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
</bean>

// numberGuess
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
</bean>

// 引用其他bean
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
    <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
</bean>

Annotation

常用的@Value注解实可使用SpEL表达式:

1
2
@Value("#{ systemProperties['user.region'] }")
private String defaultLocale;

参考语法

Literal Expressions

支持下列几种:

  • strings
  • numeric values: integer (intor long), hexadecimal (intor long), real (floator double)
  • boolean values: trueor false
  • null
1
2
3
4
// evaluates to "Tony's Pizza"
String pizzaParlor = (String) parser.parseExpression("'Tony''s Pizza'").getValue();

double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();

Properties, Arrays, Lists, Maps, and Indexers

属性首字母不区分大小写,下例也可表示为Birthdate.Year + 1900

1
2
// evaluates to 1856
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);

数组和列表的内容是通过使用[]获得的,如下例所示:

1
2
3
4
5
6
7
8
9
// 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");

Inline Lists

可以直接通过{}来表示lists

1
2
3
4
// 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);

Inline Maps

可以直接通过{key:value}来表示maps,{:}表示一个空的map。

1
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);

Array Construction(数组构造)

1
2
3
4
5
6
7
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);

Methods(方法调用)

可以通过常规的Java语法来进行方法调用:

1
2
3
4
5
6
// 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);

Operators(运算符)

类型操作符
算术运算+, -, *, /, %, ^, div, mod
关系运算<, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge
逻辑运算and, or, not, &&, ||, !
条件运算?:
正则matches
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);

// uses CustomValue:::compareTo
boolean trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean.class);

注意: 当大于小于与null进行比较的时候,遵循这么一个规则:null相当于什么都没有,所以所有值都大于null,任意值都不小于null,即x > null永远为truex < null永远为false.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// evaluates to false
boolean falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression(
        "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression(
        "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

注意: 原始类型要小心,因为它们会立即被装箱到它们的包装类型上。1 instanceof T(int)等于false1 instanceof T(Integer)等于true

1
2
3
4
5
6
7
// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

数学运算符

+号可用于数字和字符,-``*``/``%``^仅能用于数字,同时保持正常的运算优先级

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class);  // 2

String testString = parser.parseExpression(
        "'test' + ' ' + 'string'").getValue(String.class);  // 'test string'

// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class);  // 4

double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class);  // -9000

// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class);  // 6

赋值运算符

=setValue等价

1
2
3
4
5
6
7
8
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");

// alternatively
String aleks = parser.parseExpression(
        "name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);

Types

你可以使用特殊的T操作符来指定一个java.lang.Class(类型)的实例。静态方法也是通过使用这个操作符来调用的。StandardEvaluationContext使用TypeLocator来寻找类型,而StandardTypeLocator(可以被替换)是在了解java.lang包的情况下建立的。这意味着对java.lang包内类型的T()引用不需要完全限定,但所有其他类型的引用必须限定。

1
2
3
4
5
6
7
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);

Constructors

可以通过new调用构造器方法,如果不是java.lang包中的类,需要使用完整的类名

1
2
3
4
// create new Inventor instance within the add() method of List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor(
            'Albert Einstein', 'German'))").getValue(societyContext);

Variables(变量)

通过#variables的形式来引用变量,通过在EvaluationContextsetVariable可以设置变量

1
2
3
4
5
6
7
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"

通过#this引用当前对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 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);

Functions(自定义函数)

通过EvaluationContext可以注册自定义的方法

1
2
3
4
Method method = ...;

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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));
        }
        return backwards.toString();
    }
}

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

Bean References(Bean引用)

如果evaluation context中已经配置过bean解析器,可以使用@来查找beans

1
2
3
4
5
6
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);

如果需要factory bean,需要用&

1
2
3
4
5
6
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);

Ternary Operator (If-Then-Else)(三目运算)

1
2
3
4
5
6
7
8
9
parser.parseExpression("name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");

expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
        "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";

String queryResultString = parser.parseExpression(expression)
        .getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"

The Elvis Operator

简化版的三目运算符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Nikola Tesla

tesla.setName(null);
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Elvis Presley

@Value("#{systemProperties['pop3.port'] ?: 25}")
// 如果定义了pops.port则取其值,否则取25

Safe Navigation Operator

Groovy中吸取过来为了避免出现空指针的情况,js中也存在类似语法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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!!!

Collection Selection(集合选择器)

Selection是一种强大的表达式功能,可让您通过从其条目中进行选择来将源集合转换为另一个集合。其语法为.?[selectionExpression],支持只要实现了java.lang.Iterable或者java.util.Map的类。

1
2
3
4
List<Inventor> list = (List<Inventor>) parser.parseExpression(
        "members.?[nationality == 'Serbian']").getValue(societyContext);

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

如果获取首个匹配的元素语法为.^[selectionExpression],获取最后一个语法为.$[selectionExpression]

Collection Projection

集合映射,相当于从一个集合中通过子表达式获得一个新的集合。语法为.![projectionExpression]

1
2
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");

Expression templating(表达式模板)

表达式模板可以混合普通字符串和其他解析表达式模块,每一个块都用#{}来进行区分:

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

// evaluates to "random number is 0.7038186818312008"

parseExpression()方法的第二个参数是 ParserContext 类型。ParserContext接口被用来影响表达式的解析方式,以支持表达式模板化功能。TemplateParserContext是这么定义的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class TemplateParserContext implements ParserContext {

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

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

    public boolean isTemplate() {
        return true;
    }
}

源码分析

总结

SpEL是一种功能强大、支持良好的表达式语言,可以在Spring组合的所有产品中使用。我们可以用它来配置Spring应用程序,也可以用它来编写解析器,以便在任何应用程序中执行更普遍的任务。

参考文档

  1. SpEL官方文档地址:Spring Expression Language
  2. Spring Expression Language Guide
最后更新于 Jan 07, 2024 00:00 UTC
吾生也有涯,而知也无涯
Built with Hugo
主题 StackJimmy 设计