运算符 (Operators)
Spring 表达式语言支持以下几种运算符:
关系运算符 (Relational Operators)
SpEL 支持标准的关系运算符:等于 (==)、不等于 (!=)、小于 (<)、小于或等于 (<=)、大于 (>) 和大于或等于 (>=)。这些运算符既适用于数值类型,也适用于实现了 Comparable 接口的类型。
以下是一些示例:
// 评估结果为 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);// 评估结果为 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 被视为“无”(而不是零)。因此,任何其他值始终大于 null(X > null 始终为 true),并且没有任何其他值小于 null(X < null 始终为 false)。
如果你更倾向于数值比较,请避免对可能为 null 的数值进行此类比较,而应改用与零的比较(例如 X > 0 或 X < 0)。
每个符号运算符也可以通过纯文本等效项来指定。这可以避免在嵌入表达式的文档类型(如 XML 文档)中由于符号具有特殊含义而产生的问题。文本等效项如下:
lt(<)gt(>)le(<=)ge(>=)eq(==)ne(!=)
所有的文本运算符都是不区分大小写的。
除了标准的关系运算符外,SpEL 还支持 between、instanceof 和基于正则表达式的 matches 运算符。示例如下:
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);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)警告
between运算符的语法是<input> between {<range_begin>, <range_end>},它实际上是<input> >= <range_begin> && <input> <= <range_end>的简写。因此,1 between {1, 5}为true,而1 between {5, 1}为false。- 在使用
instanceof时处理基本类型要小心,因为它们会被立即装箱。例如,1 instanceof T(int)为false,而1 instanceof T(Integer)为true。
逻辑运算符 (Logical Operators)
SpEL 支持以下逻辑(布尔)运算符:
and(&&)or(||)not(!)
所有的文本运算符均不区分大小写。示例如下:
// -- 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);// -- 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)
你可以对字符串使用以下运算符:
- 拼接 (
+) - 减法 (
-):用于从包含单个字符的字符串中减去数值。 - 重复 (
*)
// 结果为 "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);// 结果为 "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++) 形式用于可写的变量或属性。
// 指数幂: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);// 指数幂
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 内部完成。
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);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 的实现,你可以让这些操作支持其他类型。
例如,如果我们想重载加法 (+) 运算符以允许两个列表进行拼接:
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("不支持该操作");
}
}启用方式如下:
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、gt、eq 等文本运算符能显著提高配置文件的可读性,避免繁琐的 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 > null 是 true,因为“有”比“无”多。这种处理方式在处理可选配置或数据库结果时非常有用,但如果不了解这一规则,可能会在条件判断中产生意外逻辑。
5. 运算符重载的使用场景
虽然运算符重载会禁用编译优化,但在以下场景中非常强大:
- 矩阵运算库集成:让
matrixA + matrixB合法化。 - 业务领域对象拼接:例如两个
Money对象相加,或两个PermissionSet合并。 - DSL 构建:在编写面向非程序员的业务规则脚本时,运算符重载能提供极佳的表达力。