Spring学习笔记:Spring SPEL表达式语言深入的学习和使用
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;}@Overridepublic 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;}@Overridepublic String toString() {return "AnnotationDemo2{" +"birthplace='" + birthplace + '\'' +", birthDate=" + birthDate +", birthTime=" + birthTime +'}';}
}