Spring学习笔记:Spring SPEL表达式语言深入的学习和使用

发布于:2025-07-19 ⋅ 阅读:(14) ⋅ 点赞:(0)

1 SPEL的概述
Spring的表达式语言,SPEL Spring Expression Language,是一种功能强大的表达式语言,SPEL语言类似于EL表达式。支持在程序运行时查询和操作数据可以节省Java代码。

SPEL表达式语言是一种Spring专门创建的语言,SPEL和Spring框架没有任何耦合关系,SPEL可以单独使用。

SPEL有三种使用方式,XML配置,注解配置,独立代码使用SPEL。最常见到的前两种使用方式。可以求值,正则匹配,创建对象,调用方法,引用bean等操作。

2 SPEL第一例
使用SPEL也要引用SPEL的表达式的maven依赖
如果和Spring一起使用,不需要单独引用SPEL的依赖,只需要这个spring-context依赖。

如果单独使用SPEL,只需要spring-expression的依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>${spring-framework.version}</version>
    <scope>compile</scope>
</dependency>

ExpressionParser接口被称为“表达式解析器”接口,负责分析表达式字符串,上面案例中我们传递的字符串参数就被称为“表达式字符串”。ExpressionParser的实现就是一个具体的表达式解析器。分析完毕之后将会返回一个Expression实例。

3 EnvaluationContext
SPEL也有自己的EnvaluationContext,称为上下文,用于计算表达式以解析属性、方法或字段并帮助执行类型转换。Spring提供了两种容器实现:
1 SimpleEnvaluationContext:EnvaluationContext简单实现,侧重基本SPEL功能实现,Java类型,构造函数和bean引用功能等不支持。

2 StandardEnvalutaionContext:具有全部SPEL功能和配置选项

4 SPEL的语法
4.1字面量表达式
SPEL支持字面量表达式,支持
(1)字符串,字符串放在外层参数字符串中时,应该使用’’包裹
(2)数值,int范围整数、double浮点数、E科学(指数)计数法、十六进制。需要注意的是,转换的类型都是包装类型,不能直接进行基本类型的转换,并且整数类型不可超出int范围。
(3)boolean或null

4.2属性导航
SPEL支持对象属性导航

4.3 集合导航
SPEL支持集合元素导航,array数组和Collection集合中元素内容是使用[index]表示法获得的,index表示索引。

Map集合的value是通过[key]获取的,注意这里的key如果是字符串,那么需要加上’’包裹。也可以引用其他变量作为key。
  array数组和Collection集合需要注意索引越界,将会抛出异常,map如果指定的key错误,那么会返回null。

@Test
public void collectionNavigating() {
    Object o = new Object();
    SpelBean spelBean = getSpelBean(o);
    ExpressionParser parser = new SpelExpressionParser();
    StandardEvaluationContext context = new StandardEvaluationContext();
    context.setVariable("spelBean", spelBean);
    context.setVariable("o", o);
    //支持集合导航

    //array和Collection使用[index]
    System.out.println(parser.parseExpression("#spelBean.strings[2]").getValue(context));
    System.out.println(parser.parseExpression("#spelBean.stringList[2]").getValue(context));
    System.out.println(parser.parseExpression("#spelBean.stringSet[2]").getValue(context));

    //map使用[key]
    //注意,我们的map中key是一个为"1"的字符串,因此这里需要带上''
    System.out.println(parser.parseExpression("#spelBean.objectObjectMap['1']").getValue(context));
    System.out.println(parser.parseExpression("#spelBean.objectObjectMap[2]").getValue(context));
    //key也可以引用内部变量作为key   返回结果如果在参数中指定为Optional类型,就不需要我们强转了
    Optional value = parser.parseExpression("#spelBean.objectObjectMap[#o]").getValue(context, Optional.class);
    System.out.println(value);

    System.out.println(parser.parseExpression("#spelBean.properties['1']").getValue(context));
    System.out.println(parser.parseExpression("#spelBean.properties['2']").getValue(context));

    System.out.println("------------设置属性值------------");

    parser.parseExpression("#spelBean.strings[2]").setValue(context,"newValue1");
    System.out.println(parser.parseExpression("#spelBean.strings[2]").getValue(context));
    parser.parseExpression("#spelBean.objectObjectMap['1']").setValue(context,"newValue2");
    System.out.println(parser.parseExpression("#spelBean.objectObjectMap['1']").getValue(context));
}

private SpelBean getSpelBean(Object o) {
    SpelBean spelBean = new SpelBean();
    String[] strings = new String[]{"1", "2", "4", "4"};
    List<String> stringList = Arrays.asList(strings);
    Set<String> stringSet = new HashSet<>(stringList);
    Map<Object, Object> objectObjectMap = new HashMap<>();
    objectObjectMap.put("1", 11);
    objectObjectMap.put(2, 22);
    objectObjectMap.put(3, 33);
    objectObjectMap.put(o, Optional.empty());
    Properties properties = new Properties();
    properties.setProperty("1", "111");
    properties.setProperty("2", "222");
    properties.setProperty("3", "333");
    properties.setProperty("4", "444");
    spelBean.setStrings(strings);
    spelBean.setStringList(stringList);
    spelBean.setStringSet(stringSet);
    spelBean.setObjectObjectMap(objectObjectMap);
    spelBean.setProperties(properties);
    return spelBean;
}

4.4 内联list
使用{} 和特定拆分符号,直接在表达式中直接表示列表,另外{} 表示一个空的list集合,可以实现集合嵌套。

@Test
public void inlineList() {
    ExpressionParser parser = new SpelExpressionParser();
    List value = parser.parseExpression("{1,2,3,4}").getValue(List.class);
    System.out.println(value);
    System.out.println(value.getClass());

    //集合嵌套
    List value1 = parser.parseExpression("{{},{'a','b'},{'x','y'}}").getValue(ArrayList.class);
    System.out.println(value1);
    System.out.println(value1.getClass());
    //{}本身就是一个集合,可以实现集合嵌套
    for (Object o : value1) {
        System.out.println(o.getClass());
        List list0= (List) o;
        System.out.println(list0);
    }

    //一定要注意,如果我们设置预期类型为List,或者不设置返回类型,那么实际上返回是一个java.util.Collections.UnmodifiableList的实例
    //该集合是不可变的,包括新增、删除、修改操作,都将直接抛出UnsupportedOperationException,只能用来遍历
    //因此,如果需要后续对集合进行操作,建议使用具体的集合类型接收,比如ArrayLsit
    //value.set(1, 2);
}

4.5 内联map
使用{key : value}表示的是map,元素之间使用“,”分隔,另外本身{:}本身也是一个map。

@Test
public void inlineMap() {
    StandardEvaluationContext context = new StandardEvaluationContext();
    context.setVariable("value", "value");
    ExpressionParser parser = new SpelExpressionParser();
    //常量数据集合
    System.out.println(parser.parseExpression("{1:2,2:3,3:4}").getValue(context, Map.class).getClass());
    //引用到容器变量的集合
    Map value = parser.parseExpression("{1:#value,2:3,3:4}").getValue(context, Map.class);
    System.out.println(value);
    System.out.println(value.getClass());

    System.out.println("--------map嵌套----------");
    //嵌套
    //指定HashMap类型,实际上返回的是一个LinkedHashMap类型
    HashMap value1 = parser.parseExpression("{{1:1,2:2}:2,2:3,3:{'xx':'y',{1,2}:4}}").getValue(HashMap.class);
    System.out.println(value1);
    System.out.println(value1.getClass());
    //{}本身就是一个集合,可以实现集合嵌套
    value1.forEach((k, v) -> {
        System.out.println(k);
        System.out.println(k.getClass());
        System.out.println(v);
        System.out.println(v.getClass());
    });

    // 一定要注意,对于常量数据组成的集合(即集合的key或者value没有引用到容器中的变量),如果我们设置预期返回类型为List,
    // 或者不设置预期返回类型,那么实际上返回是一个java.util.Collections.UnmodifiableMap的实例,该集合是不可变的,
    // 包括新增、删除、修改操作,都将直接抛出UnsupportedOperationException,只能用来遍历,这么做是为了提升SPEL的解析性能。
    // 因此,如果需要后续对集合进行操作,建议使用具体的集合类型接收,比如HashMap.class。
    value.put(1, 2);
}

4.6 构造数组
在SPEL表达式,用Java代码的方式构造数组,即new一个数组。
另外,数组可以自动转换为集合,集合也可以自动转换为数组

@Test
public void arrayConstruction() {
    ExpressionParser parser = new SpelExpressionParser();
    //初始化数组
    int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue();
    System.out.println(Arrays.toString(numbers1));
    //数组可以自动的转换为集合
    System.out.println(parser.parseExpression("new int[4]").getValue(List.class));
    //集合也可以自动的转换为数组
    System.out.println(parser.parseExpression("{1,2,3,4}").getValue(int[].class));

    //初始化数组并赋值
    int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3,3}").getValue();
    System.out.println(Arrays.toString(numbers2));

    //数组可以自动的转换为集合
    System.out.println(parser.parseExpression("new int[]{1,2,3,3}").getValue(List.class));
    System.out.println(parser.parseExpression("new int[]{1,2,3,3}").getValue(Set.class));

    //二维数组
    int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue();
    System.out.println(Arrays.deepToString(numbers3));
    //目前不支持初始化生成多维数组并赋值
    //int[][] numbers4 = (int[][]) parser.parseExpression("new int[][]{{1,2},{3,4},{3,4,5,6}}").getValue();
}

4.7 方法调用
在SPEL表达式使用典型的Java编程调用方法

@Test
public void methodInvoke() {
    ExpressionParser parser = new SpelExpressionParser();
    //对字符串调用方法
    //截取
    System.out.println(parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class));
    //拆分数组
    System.out.println(Arrays.toString(parser.parseExpression("'a,b,c,c'.split(',')").getValue(String[].class)));
    //虽然split方法返回一个数组,实际上也可以转换为集合,自动转换机制
    System.out.println(parser.parseExpression("'a,b,c,c'.split(',')").getValue(Set.class));

    SpelBean spelBean = new SpelBean();
    spelBean.setProperty1("property1");
    spelBean.setProperty2(11);
    //默认Context调用方法
    System.out.println(parser.parseExpression("getProperty1()").getValue(spelBean, String.class));

    //指定Context调用方法
    StandardEvaluationContext context = new StandardEvaluationContext();
    context.setVariable("spelBean", spelBean);
    context.setRootObject(spelBean);
    System.out.println(parser.parseExpression("getProperty1()").getValue(context, String.class));
}

4.8运算符支持
SPEL支持非常多种的运算符操作,Java语言的还包括Groovy语言

4.8.1 算数运算符
SPEL支持在数值之间使用算数运算符(Mathematical Operators)。加法(+)、减法(-)、乘法(*)、除法(/)、指数(^)、取模(%)运算符。

4.8.2 关系运算符
 SPEL可以在数值之间使用关系运算符(Relational Operators):< > <= >= == !=。
  null算作“不存在”,与null比较大小时,任何实数都大于null,null等于null,null不能参与数值计算。

@Test
public void relationalOperators() {
    ExpressionParser parser = new SpelExpressionParser();
    System.out.println(parser.parseExpression("6>2").getValue());
    System.out.println(parser.parseExpression("6>=2").getValue());
    System.out.println(parser.parseExpression("6<2").getValue());
    System.out.println(parser.parseExpression("6<=2").getValue());
    System.out.println(parser.parseExpression("6==2").getValue());
    System.out.println(parser.parseExpression("6!=2").getValue());
    //结合算数运算符
    System.out.println(parser.parseExpression("6!=2*3").getValue());
    System.out.println(parser.parseExpression("6+1!=2*3").getValue());
    System.out.println(parser.parseExpression("6+1>2*3").getValue());

    System.out.println("----null----");
    System.out.println(parser.parseExpression("0 gt null").getValue());
    System.out.println(parser.parseExpression("-11111>null").getValue());
    System.out.println(parser.parseExpression("null==null").getValue());
}

4.8.3 逻辑运算符
SPEL支持逻辑运算符(Logical Operators): and(&&) or(||) not(!)

4.8.4 条件运算符
支持三目运算符,和Java语法类似
关系运算?运算1(或者直接返回结果):运算2(或者直接返回结果)。支持嵌套。

4.8.5赋值运算符
和Java语法类似 就是一个“=”‘

4.8.6 instanceOf运算符
instanceof在Java语法中也被支持,用来测试某个对象是否属于某个类型。

4.8.7 matches运算符
matches运算符就是类似于Java中的String的matches方法。用于判断字符串是否匹配某个正则表达式。

4.8.8 Safe Navigation

4.9 T类型
SPEL支持使用T(classpath)的样式来表示Java的某个类的class对象,同时又可以调用该类的静态方法和字段。classpath一般填写类的全限定名,java.lang包中的类只需要写简单类名即可。

@Test
public void tClass() {
    ExpressionParser parser = new SpelExpressionParser();
    //调用String的join静态方法
    System.out.println(parser.parseExpression("T(String).join('_','@','@')").getValue());
    //调用Math的random静态方法
    System.out.println(parser.parseExpression("T(Math).random()*100").getValue());
    //调用Integer的MAX_VALUE静态字段
    System.out.println(parser.parseExpression("T(Integer).MAX_VALUE").getValue());
}

4.10 new 构造器
SPEL支持使用new 运算符调用构造器,除了Java.lang包中的类之外,其它类型都应该使用全限定类名。

4.11 变量
#variableName语法引用计算上下文容器中的变量(Variables)。变量是通过在EvaluationContext的实现并调用setVariable方法设置的。

4.11.1 #this 和#root
#this和#root是预定义的变量,#this表示引用的当前被计算的对象,#root表示的是上下文容器的根对象rootObject(默认为null)。

4.12 函数

4.13 Bean引用

4.14 集合筛选

4.15 集合投影

4.16 表达式模版
表达式模板(Expression templates)允许将文本与一个或多个计算表达式组合在一起。每个计算表达式都用可定义的前缀和后缀字符分隔,常见的选择是使用“#{”作为表达式的前缀,“}”作为表达式的后缀,中间的部分就是计算表达式。

5 结合Spring使用
SpEL可以Spring基于XML或基于注释的配置元数据一起使用

在基于XML或者注释这两种情况定义表达式的语法 # {.表达式字符串 },有一点区别。
通过bean name引用容器中的bean的不需要加上#或者@前缀了,按bean name引用即可。并且,在SPEL表达式字符串中,也可以使用${key}引用外部属性的配置,注意如果是字符串,建议加上’ '限制。

5.1 XML的配置
在XML配置中,可以使用SPEL表达式设置属性或构造函数参数值。

birthplace=100.0
property1=one
property2=10
strings=strings
stringList=stringList
stringSet={1,2,3,3,'${strings}'}
objectObjectMap.key=new Object()
objectObjectMap.value={1,2,3}
properties={1:2,2:3,3:4}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--引入配置文件 SPEL中可以使用${key}获取配置文件的属性 注意如果是字符串需要加上'' 。如果value中仅仅注入属性文件的字面量值,那么使用${key}就行了-->
    <context:property-placeholder location="classpath:kv.properties"/>

    <!--XML使用SPEL表达式-->
    <bean class="com.spring.spel.Birth" name="birth">
        <constructor-arg name="birthplace" value="#{T(java.lang.Math).random() * ${birthplace}}"/>
        <constructor-arg name="birthDate" value="#{T(java.time.LocalDate).now()}"/>
        <constructor-arg name="birthTime" value="#{T(java.time.LocalTime).now()}"/>

        <!--引用预定义bean systemProperties的属性,systemProperties是一个map-->
        <property name="birthplace" value="#{systemProperties['sun.desktop']}"/>
    </bean>

    <bean class="com.spring.spel.SpelBean" name="spelBean">
        <!--SPEL引用 容器中的bean-->
        <constructor-arg name="birth" value="#{birth}"/>
        <!--引用bean 属性-->
        <constructor-arg name="property1" value="#{birth.birthplace+'${property1}'}"/>

        <property name="property2"
                  value="#{T(java.util.concurrent.ThreadLocalRandom).current().nextInt(${property2})}"/>
        <!--{}list集合实际上可以自动转换为数组-->
        <property name="strings" value="#{{1,2,3,'${strings}'}}"/>
        <!--collection list 集合-->
        <property name="stringList" value="#{{1,2,3,3,'${strings}'}}"/>
        <!--collection set 集合 自动去重-->
        <property name="stringSet" value="#{${stringSet}}"/>

        <!--map 集合-->
        <property name="objectObjectMap"
                  value="#{{${property1}:2,'2 -2':3,${objectObjectMap.key}:${objectObjectMap.value},'xx':'yy'}}"/>
        <!--map 集合-->
        <property name="properties" value="#{${properties}}"/>
    </bean>
</beans>

5.2 注解配置

注解配置和XML配置中的SPEL语法都是差不多的!注解配置使用@Value注解,@Value注解可以标注在字段,方法、方法/构造器的参数上。

@Configuration
//引入配置文件
@PropertySource(value = "classpath:kv.properties", encoding = "UTF-8")
@ComponentScan("com.spring.spel")
public class AnnotationDemo {
    private String property1;
    @Value("#{T(java.util.concurrent.ThreadLocalRandom).current().nextInt(${property2})}")
    private int property2;
    private AnnotationDemo2 annotationDemo2;
    @Value("#{{1,2,3,'${strings}'}}")
    private String[] strings;
    private List<String> stringList;
    private Set<String> stringSet;
    @Value("#{{${property1}:2,'2 -2':3,${objectObjectMap.key}:${objectObjectMap.value},'xx':'yy'}}")
    private Map<Object, Object> objectObjectMap;
    @Value("#{${properties}}")
    private Properties properties;

    @Value("#{{'1','0','1','1'}}")
    private char[] chars;
    @Value("#{{1,2,3,0,11,2}}")
    private byte[] bytes;
    @Value("#{{1,0,1,1}}")
    private float[] floats;
    @Value("#{{1,0,1,1}}")
    private double[] doubles;
    @Value("#{{'1','0','1','1'}}")
    private boolean[] booleans;


    public AnnotationDemo(@Value("#{annotationDemo2.birthplace+'${property1}'}") String property1, @Value(
            "#{annotationDemo2}") AnnotationDemo2 annotationDemo2) {
        this.property1 = property1;
        this.annotationDemo2 = annotationDemo2;
    }

    @Value("#{{1,2,3,3,'${strings}'}}")
    public void setStringList(List<String> stringList) {
        this.stringList = stringList;
    }

    public void setStringSet(@Value("#{${stringSet}}") Set<String> stringSet) {
        this.stringSet = stringSet;
    }

    @Override
    public String toString() {
        return "AnnotationDemo{" +
                "property1='" + property1 + '\'' +
                ", property2=" + property2 +
                ", annotationDemo2=" + annotationDemo2 +
                ", strings=" + Arrays.toString(strings) +
                ", stringList=" + stringList +
                ", stringSet=" + stringSet +
                ", objectObjectMap=" + objectObjectMap +
                ", properties=" + properties +
                ", chars=" + Arrays.toString(chars) +
                ", bytes=" + Arrays.toString(bytes) +
                ", floats=" + Arrays.toString(floats) +
                ", doubles=" + Arrays.toString(doubles) +
                ", booleans=" + Arrays.toString(booleans) +
                '}';
    }
}
//…………………………
@Component
public class AnnotationDemo2 {
    private String birthplace;
    private LocalDate birthDate;
    @Value("#{T(java.time.LocalTime).now()}")
    private LocalTime birthTime;

    @Value("#{T(java.time.LocalDate).now()}")
    public void setBirthDate(LocalDate birthDate) {
        this.birthDate = birthDate;
    }

    public AnnotationDemo2(@Value("#{T(java.lang.Math).random() * ${birthplace}}")
                                   String birthplace) {
        this.birthplace = birthplace;
    }

    /**
     * 导航的属性,要求必须是public修饰的或者提供了相应的getter方法
     */
    public String getBirthplace() {
        return birthplace;
    }

    @Override
    public String toString() {
        return "AnnotationDemo2{" +
                "birthplace='" + birthplace + '\'' +
                ", birthDate=" + birthDate +
                ", birthTime=" + birthTime +
                '}';
    }
}



网站公告

今日签到

点亮在社区的每一天
去签到