【Spring】SpEL 一 语法总结与示例

前言

Spring Expression Language (SpEL) Spring 表达式

本文旨在总结 SpEL 的常用语法并给出对应的示例 demo

核心类打点

  • ExpressionParser,解析器顶层接口,负责解析 SpEL 表达式,并返回 Expression,主要使用实现类 SpelExpressionParser,同时可以接受参数 ParserContext
  • Expression,表达式解析结果封装,主要关注核心 API getValue,返回解析结果,允许指定返回类型,可以接受参数 EvaluationContext
  • ParserContext,解析器上下文,可以指定解析语法的前后缀等信息
  • EvaluationContext,解析上下文,可以设置解析语法的上下文环境,诸如指定变量、变量函数、根对象等,主要实现类 StandardEvaluationContext
  • PropertyAccessor,全路径 org.springframework.expression.PropertyAccessorbeans 包下也有一个 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));
    }

这里把 ListMap内联映射 示例放到一起,不难理解,支持嵌套

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)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值