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 +
'}';
}
}