Skip to content

运算符 (Operators)

Spring 表达式语言支持以下几种运算符:

关系运算符 (Relational Operators)

SpEL 支持标准的关系运算符:等于 (==)、不等于 (!=)、小于 (<)、小于或等于 (<=)、大于 (>) 和大于或等于 (>=)。这些运算符既适用于数值类型,也适用于实现了 Comparable 接口的类型。

以下是一些示例:

java
// 评估结果为 true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);

// 评估结果为 false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);

// 评估结果为 true(字符串比较)
boolean trueValueStr = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);

// 使用 CustomValue.compareTo 进行比较
boolean customCompare = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean.class);
kotlin
// 评估结果为 true
val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java)

// 评估结果为 false
val falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean::class.java)

// 评估结果为 true(字符串比较)
val trueValueStr = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)

// 使用 CustomValue.compareTo 进行比较
val customCompare = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean::class.java)

关于 Null 的比较规则

null 进行大于或小于比较时遵循一个简单的规则:null 被视为“无”(而不是零)。因此,任何其他值始终大于 nullX > null 始终为 true),并且没有任何其他值小于 nullX < null 始终为 false)。

如果你更倾向于数值比较,请避免对可能为 null 的数值进行此类比较,而应改用与零的比较(例如 X > 0X < 0)。

每个符号运算符也可以通过纯文本等效项来指定。这可以避免在嵌入表达式的文档类型(如 XML 文档)中由于符号具有特殊含义而产生的问题。文本等效项如下:

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

所有的文本运算符都是不区分大小写的。

除了标准的关系运算符外,SpEL 还支持 betweeninstanceof 和基于正则表达式的 matches 运算符。示例如下:

java
boolean result;

// 评估结果为 true
result = parser.parseExpression("1 between {1, 5}").getValue(Boolean.class);

// 评估结果为 false
result = parser.parseExpression("1 between {10, 15}").getValue(Boolean.class);

// 字符串范围比较,结果为 true
result = parser.parseExpression("'elephant' between {'aardvark', 'zebra'}").getValue(Boolean.class);

// 评估结果为 true
result = parser.parseExpression("123 instanceof T(Integer)").getValue(Boolean.class);

// 正则表达式匹配,结果为 true
result = parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
kotlin
var result: Boolean

// 评估结果为 true
result = parser.parseExpression("1 between {1, 5}").getValue(Boolean::class.java)

// 评估结果为 false
result = parser.parseExpression("1 between {10, 15}").getValue(Boolean::class.java)

// 字符串范围比较,结果为 true
result = parser.parseExpression("'elephant' between {'aardvark', 'zebra'}").getValue(Boolean::class.java)

// 评估结果为 true
result = parser.parseExpression("123 instanceof T(Integer)").getValue(Boolean::class.java)

// 正则表达式匹配,结果为 true
result = parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)

警告

  1. between 运算符的语法是 <input> between {<range_begin>, <range_end>},它实际上是 <input> >= <range_begin> && <input> <= <range_end> 的简写。因此,1 between {1, 5}true,而 1 between {5, 1}false
  2. 在使用 instanceof 时处理基本类型要小心,因为它们会被立即装箱。例如,1 instanceof T(int)false,而 1 instanceof T(Integer)true

逻辑运算符 (Logical Operators)

SpEL 支持以下逻辑(布尔)运算符:

  • and (&&)
  • or (||)
  • not (!)

所有的文本运算符均不区分大小写。示例如下:

java
// -- AND --
// 评估结果为 false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);

// -- OR --
// 评估结果为 true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);

// -- NOT --
// 评估结果为 false
boolean notTrue = parser.parseExpression("!true").getValue(Boolean.class);
kotlin
// -- AND --
val falseValue = parser.parseExpression("true and false").getValue(Boolean::class.java)

// -- OR --
val trueValue = parser.parseExpression("true or false").getValue(Boolean::class.java)

// -- NOT --
val notTrue = parser.parseExpression("!true").getValue(Boolean::class.java)

字符串运算符 (String Operators)

你可以对字符串使用以下运算符:

  • 拼接 (+)
  • 减法 (-):用于从包含单个字符的字符串中减去数值。
  • 重复 (*)
java
// 结果为 "hello world"
String helloWorld = parser.parseExpression("'hello' + ' ' + 'world'").getValue(String.class);

// 结果为 'a' ('d' 的 ASCII 值减 3)
char ch = parser.parseExpression("'d' - 3").getValue(char.class);

// 结果为 "abcabc"
String repeated = parser.parseExpression("'abc' * 2").getValue(String.class);
kotlin
// 结果为 "hello world"
val helloWorld = parser.parseExpression("'hello' + ' ' + 'world'").getValue(String::class.java)

// 结果为 'a'
val ch = parser.parseExpression("'d' - 3").getValue(Character::class.java)

// 结果为 "abcabc"
val repeated = parser.parseExpression("'abc' * 2").getValue(String::class.java)

数学运算符 (Mathematical Operators)

你可以对数值使用以下运算符。SpEL 遵循标准的运算符优先级。

  • 加法 (+)
  • 减法 (-)
  • 自增 (++)
  • 自减 (--)
  • 乘法 (*)
  • 除法 (/) 或 div
  • 取模 (%) 或 mod
  • 指数幂 (^)

注意

自增和自减运算符可以以 前缀 (++A) 或 后缀 (A++) 形式用于可写的变量或属性。

java
// 指数幂:2 的 31 次方减 1
int maxInt = parser.parseExpression("(2^31) - 1").getValue(int.class); // Integer.MAX_VALUE

// 优先级:1+2- (3*8) = -21
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(int.class);
kotlin
// 指数幂
val maxInt = parser.parseExpression("(2^31) - 1").getValue(Int::class.java)

// 优先级
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java)

赋值运算符 (Assignment Operator)

要设置属性,请使用赋值运算符 (=)。这通常在调用 setValue 时进行,但也可以在调用 getValue 内部完成。

java
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

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

// 或者在 getValue 过程中赋值
String aleks = parser.parseExpression("name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
kotlin
val inventor = Inventor()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()

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

// 或者
val aleks = parser.parseExpression("name = 'Aleksandar Seovic'").getValue(context, inventor, String::class.java)

运算符重载 (Overloaded Operators)

默认情况下,SpEL 的数学运算(加、减、乘、除、取模、幂)仅支持数值类型。通过提供 OperatorOverloader 的实现,你可以让这些操作支持其他类型。

例如,如果我们想重载加法 (+) 运算符以允许两个列表进行拼接:

java
public class ListConcatenation implements OperatorOverloader {
	@Override
	public boolean overridesOperation(Operation operation, Object left, Object right) {
		return (operation == Operation.ADD && left instanceof List && right instanceof List);
	}

	@Override
	public Object operate(Operation operation, Object left, Object right) {
		if (operation == Operation.ADD && left instanceof List list1 && right instanceof List list2) {
			List result = new ArrayList(list1);
			result.addAll(list2);
			return result;
		}
		throw new UnsupportedOperationException("不支持该操作");
	}
}

启用方式如下:

java
StandardEvaluationContext context = new StandardEvaluationContext();
context.setOperatorOverloader(new ListConcatenation());

// 结果为一个新列表:[1, 2, 3, 4, 5]
List result = parser.parseExpression("{1, 2, 3} + {4, 5}").getValue(context, List.class);

警告

使用重载运算符的任何表达式都 无法被编译 为字节码。


补充教学

1. XML 安全性与文本运算符

在 XML 配置文件中使用 SpEL 时(例如 <property name="test" value="#{1 lt 2}" />),如果直接使用 < 符号,会导致 XML 解析错误,除非你使用 &lt; 转义。 使用 ltgteq 等文本运算符能显著提高配置文件的可读性,避免繁琐的 XML 转义字符。

2. 关于 ^ 指数运算符的重大区别

在 Java 中,^按位异或 (XOR) 运算符。但在 SpEL 中,^ 被定义为 指数幂 (Power) 运算符。

  • 如果你习惯于 Java 开发,千万不要在 SpEL 中用 ^ 做位运算。
  • 如果需要进行位运算,SpEL 并没有提供原生符号,通常需要通过 T(CustomBitUtils).xor(a, b) 或类似方式调用静态方法。

3. 正则表达式的性能影响

matches 运算符在背后调用的是 Java 的 Pattern.matches()

  • 注意:这会引发全匹配,而不是局部匹配。
  • 性能:如果在高频执行(如网关拦截器或数万次循环计算)中使用复杂的正则表达式,可能会产生显著的 CPU 开销。建议针对这种场景预先编译好 Pattern 并在 Java 代码中处理。

4. null 比较的哲学

SpEL 的 null 比较逻辑非常独特:null 不是 0,它是“无”。 这意味着 1 > nulltrue,因为“有”比“无”多。这种处理方式在处理可选配置或数据库结果时非常有用,但如果不了解这一规则,可能会在条件判断中产生意外逻辑。

5. 运算符重载的使用场景

虽然运算符重载会禁用编译优化,但在以下场景中非常强大:

  • 矩阵运算库集成:让 matrixA + matrixB 合法化。
  • 业务领域对象拼接:例如两个 Money 对象相加,或两个 PermissionSet 合并。
  • DSL 构建:在编写面向非程序员的业务规则脚本时,运算符重载能提供极佳的表达力。

Based on Spring Framework.