前言
Spring Expression Language (SpEL)
Spring 表达式
本文旨在总结 SpEL
的常用语法并给出对应的示例 demo
核心类打点
ExpressionParser
,解析器顶层接口,负责解析SpEL
表达式,并返回Expression
,主要使用实现类SpelExpressionParser
,同时可以接受参数ParserContext
Expression
,表达式解析结果封装,主要关注核心 APIgetValue
,返回解析结果,允许指定返回类型,可以接受参数EvaluationContext
ParserContext
,解析器上下文,可以指定解析语法的前后缀等信息EvaluationContext
,解析上下文,可以设置解析语法的上下文环境,诸如指定变量、变量函数、根对象等,主要实现类StandardEvaluationContext
PropertyAccessor
,全路径org.springframework.expression.PropertyAccessor
(beans
包下也有一个PropertyAccessor
),属性访问顶层接口,Spring
默认提供了不少实现类诸如ReflectivePropertyAccessor
BeanFactoryAccessor
,也可以自定义实现
示例
直接通过示例 demo
来体会使用方式,我们以官方对使用方式的划分维度进行,示例用到的基础信息如下:
基础类 & 属性 & 方法
@Data
@AllArgsConstructor
@NoArgsConstructor
public class A {
String name;
List<String> list;
Map<String, Object> map;
B[] bs;
B b;
public static final String STR = "str";
public boolean contains(String str) {
return list.contains(str);
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class B {
String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
B b = (B) o;
return Objects.equals(getName(), b.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName());
}
}
ExpressionParser parser = new SpelExpressionParser();
static A a = new A();
@BeforeAll
public static void init() {
a.setName("a");
a.setList(
new ArrayList<>() {
{
add("1");
add("2");
}
}
);
a.setMap(
new HashMap<>() {
{
put("a", "1");
put("b", "2");
}
}
);
a.setB(new B("dd"));
a.setBs(new B[] {
new B("a")
, new B("b")
});
}
Literal Expressions
@Test
public void literal() {
String value = parser
.parseExpression("'hello world'")
.getValue(String.class);
Assertions.assertEquals("hello world", value);
Boolean aTrue = parser
.parseExpression("true")
.getValue(Boolean.class);
Assertions.assertTrue(aTrue);
}
字符串表达式解析,最简单的应用方式,支持 String
Boolean
Number
等类型的直接解析
Properties, Arrays, Lists, Maps, and Indexers
@Test
public void index() {
EvaluationContext context = new StandardEvaluationContext(a);
String s1 = parser
.parseExpression("list[1]")
.getValue(context, String.class);
Assertions.assertEquals("2", s1);
String s2 = parser
.parseExpression("bs[1].name")
.getValue(context, String.class);
Assertions.assertEquals("b", s2);
String s3 = parser
.parseExpression("map[a]")
.getValue(context, String.class);
Assertions.assertEquals("1", s3);
}
List、数组
可以依据下标进行操作,对于数组越界、对应元素初始化
等的设置可使用SpelParserConfiguration
类进行配置,本文不讨论Map
可以直接基于key
进行操作,当然字符串的key
可以省略单引号(中文不能省略)- 该示例中指定了
EvaluationContext
,定义了 根对象,因此可以直接操作对应的属性
Inline Lists & Inline Maps
@Test
public void inline() {
List list = parser
.parseExpression("{{'a', 'b'}, {'c'}}")
.getValue(List.class);
list.forEach(System.out::println);
Map map = parser
.parseExpression("{a: {a: '1', b: '2'}, b: {c: '3'}}")
.getValue(Map.class);
map.forEach((k, v) -> System.out.println(k +":"+ v));
}
这里把 List
和 Map
的 内联映射
示例放到一起,不难理解,支持嵌套
Array Construction
@Test
public void array() {
int[] ins = parser
.parseExpression("new int[] {1, 2}")
.getValue(int[].class);
Arrays.stream(ins).forEach(System.out::println);
int[] ins2 = parser
.parseExpression("new int[1][2]")
.getValue(int[].class);
}
数组构造的语法基本就是 java
语法了,其中 对于多维数组不支持直接初始化
Methods
@Test
public void method() {
Integer len = parser
.parseExpression("'a'.length")
.getValue(int.class);
Assertions.assertEquals(1, len);
String bc = parser
.parseExpression("'abc'.substring(1, 3)")
.getValue(String.class);
Assertions.assertEquals("bc", bc);
EvaluationContext context = new StandardEvaluationContext(a);
Boolean b = parser
.parseExpression("contains('1')")
.getValue(context, boolean.class);
Assertions.assertTrue(b);
}
支持方法的调用哦~
- 比如
String#length
,无参省略括号 - 有参数的方法,字符串参数用
单引号
- 在设置
EvaluationContext
后,也是可以直接调用根对象
方法的
Operators
@Test
public void operators() {
// relational operators
Boolean b1 = parser
.parseExpression("2 gt 1")
.getValue(boolean.class);
Assertions.assertTrue(b1);
Boolean b2 = parser
.parseExpression("'a' instanceof T(Integer)")
.getValue(boolean.class);
Assertions.assertFalse(b2);
// logic operators
Boolean b3 = parser
.parseExpression("!true")
.getValue(boolean.class);
Assertions.assertFalse(b3);
Boolean b4 = parser
.parseExpression("true or false")
.getValue(boolean.class);
Assertions.assertTrue(b4);
// mathematical operators
Integer i = parser
.parseExpression("1 - -3")
.getValue(int.class);
Assertions.assertEquals(4, i);
// the assignment operators
EvaluationContext context = new StandardEvaluationContext(a);
parser
.parseExpression("name = 'dd'")
.getValue(context);
Assertions.assertEquals("dd", a.getName());
parser
.parseExpression("name")
.setValue(context, "dd2");
Assertions.assertEquals("dd2", a.getName());
}
四种操作运算:
- 关系运算:
gt (>)
le (<=)
ne (!=)
not (!)
等 - 逻辑运算:
and
or
not
- 算术运算:
+
-
*
/
%
- 赋值运算,示例中结合
EvaluationContext
对根对象
进行赋值操作
Types
@Test
public void type() {
Class string = parser
.parseExpression("T(String)")
.getValue(Class.class);
Assertions.assertEquals(String.class, string);
Class a = parser
.parseExpression("T(com.example.demo.spel.csdn.A)")
.getValue(Class.class);
Assertions.assertEquals(a, A.class);
String str = parser
.parseExpression("T(com.example.demo.spel.csdn.A).STR")
.getValue(String.class);
Assertions.assertEquals("str", str);
}
- 引用
java.lang
下的类可以不写全路径 - 可以直接引用静态常量
Constructors
@Test
public void constructors() {
B b = parser
.parseExpression("new com.example.demo.spel.csdn.B('dd')")
.getValue(B.class);
Assertions.assertEquals("dd", b.getName());
EvaluationContext context = new StandardEvaluationContext(a);
parser
.parseExpression("list.add(new String('dd'))")
.getValue(context);
Boolean f = parser
.parseExpression("contains('dd')")
.getValue(context, boolean.class);
Assertions.assertTrue(f);
}
- 同理,
java.lang
下的类可以省略全名直接new
- 示例中,第二段代码在
EvaluationContext
对根对象
的属性进行填充实例操作
Variables & Functions
@Test
public void variables() throws NoSuchMethodException {
EvaluationContext context = new StandardEvaluationContext(a);
context.setVariable("name", "dd");
parser
.parseExpression("name = #name")
.getValue(context);
context.setVariable(
"print"
, this.getClass().getDeclaredMethod("print", String.class)
);
parser
.parseExpression("#print(name)")
.getValue(context);
}
public static void print(String str) {
System.out.println(str);
}
这里因为 参数变量 和 函数变量 用法相似,因此示例整合到一起
- 该示例在上下文
EvaluationContext
中进行 - 参数变量设置见示例,引用时使用符号
#
前缀即可 - 函数变量的使用相同,也是加前缀符号
#
,并且参数也可以直接引用 根对象 属性 - 我们可以发现,在
上下文
中指定根对象
后,可以直接调用它的属性、方法,可以理解为实际上调用的是#root.xxx
,见下例:
@Test
public void root() {
EvaluationContext context = new StandardEvaluationContext(a);
// context.setVariable("root", a);
String value = parser
.parseExpression("#root.name")
.getValue(context, String.class);
Assertions.assertEquals("a", value);
}
Ternary Operator (If-Then-Else)
@Test
public void ternary() {
String value = parser
.parseExpression("true ? '1' : '0'")
.getValue(String.class);
Assertions.assertEquals("1", value);
}
三目运算
The Elvis Operator
@Test
public void elvis() {
String value = parser
.parseExpression("name?:'default'")
.getValue(new B(), String.class);
Assertions.assertEquals("default", value);
}
特定 三目运算
的简写,譬如如上示例中的写法相当于 name != null ? name : 'default'
doc 解释取名 The Elvis Operator 是因为该符号很像 Elvis(猫王) 的发型?
Safe Navigation Operator
@Test
public void safe() {
String value = parser
.parseExpression("b?.name")
.getValue(new A(), String.class);
Assertions.assertNull(value);
String value1 = parser
.parseExpression("b?.name")
.getValue(a, String.class);
Assertions.assertEquals("dd", value1);
}
有效避免 NPE
,语法符号为 ?.
Collection Selection
@Test
public void selection() {
EvaluationContext context = new StandardEvaluationContext(a);
B[] bs = parser
.parseExpression("bs.?[name == 'a']")
.getValue(context, B[].class);
for (B b : bs) {
System.out.println(b);
}
}
语法符号为 .?
,注意与 NPE-Safe
语法区分,用于集合筛选
Collection Projection
@Test
public void projection() {
EvaluationContext context = new StandardEvaluationContext(a);
List value = parser
.parseExpression("bs.![name]")
.getValue(context, List.class);
value.forEach(System.out::println);
}
集合映射,语法符号 .!
,我觉得可以理解为类似 Stream#map
总结
本章节总结了 SpEL
的各种基础语法,并给出了对应的示例,限于篇幅,对 PropertyAccessor
相关的内容放到下个章节
下一篇:【【Spring】SpEL 二 PropertyAccessor 相关(BeanFactoryAccessor EnvironmentAccessor)