【Spring】SpEL 一 语法总结与示例
本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
Spring Expression Language (SpEL) Spring 表达式
本文旨在总结 SpEL 的常用语法并给出对应的示例 demo
核心类打点
ExpressionParser,解析器顶层接口,负责解析SpEL表达式,并返回Expression,主要使用实现类SpelExpressionParser,同时可以接受参数ParserContextExpression,表达式解析结果封装,主要关注核心 APIgetValue,返回解析结果,允许指定返回类型,可以接受参数EvaluationContextParserContext,解析器上下文,可以指定解析语法的前后缀等信息EvaluationContext,解析上下文,可以设置解析语法的上下文环境,诸如指定变量、变量函数、根对象等,主要实现类StandardEvaluationContextPropertyAccessor,全路径org.springframework.expression.PropertyAccessor(beans包下也有一个PropertyAccessor),属性访问顶层接口,Spring默认提供了不少实现类诸如ReflectivePropertyAccessorBeanFactoryAccessor,也可以自定义实现
示例
直接通过示例 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 (!)等 - 逻辑运算:
andornot - 算术运算:
+-*/% - 赋值运算,示例中结合
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 相关的内容放到下个章节