Spring表达式语言简称为SpEL,一种类似Ognl的对象图导航语言(对于ognl不熟悉的同学可以参考一下: Ognl系列博文 )
SeEL为Spring提供了丰富的想象空间,除了一些基本的表达式操作之外,还支持
访问bean对象
调用方法,访问(修改)类(对象)属性
计算表达式
正则匹配
…
I. 语法百科
以下内容均来自官方文档: https://docs.spring.io/spring-framework/docs/5.2.1.RELEASE/spring-framework-reference/core.html#expressions
1. 字面表达式 Spel支持strings, numeric values (int, real, hex), boolean, and null等基本类型,实例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ExpressionParser parser = new SpelExpressionParser(); String helloWorld = (String) parser.parseExpression("'Hello World'" ).getValue(); double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23" ).getValue();int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF" ).getValue();boolean trueValue = (Boolean) parser.parseExpression("true" ).getValue();Object nullValue = parser.parseExpression("null" ).getValue();
请注意,字符串需要用单引号包括,浮点数默认为double类型,用null表示null object
输出结果
1 2 3 4 5 str: Hello World double: 6.0221415E23 int: 2147483647 bool: true null: null
2. Inline List 通过{}来表明List表达式,一个空的列表直接用{}表示
1 2 3 4 5 6 7 8 ExpressionParser parser = new SpelExpressionParser(); List numbers = (List) parser.parseExpression("{1,2,3,4}" ).getValue(); System.out.println("list: " + numbers); List<List> listlOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}" ).getValue(); System.out.println("List<List> : " + listlOfLists);
输出结果
1 2 list: [1, 2, 3, 4] List<List> : [[a, b], [x, y]]
3. Inline map {key:value}来表示map表达式,空Map直接用{:}表示
1 2 3 4 5 6 7 8 9 private void map () { ExpressionParser parser = new SpelExpressionParser(); Map map = (Map) parser.parseExpression("{txt:'Nikola',dob:'10-July-1856'}" ).getValue(); System.out.println("map: " + map); Map mapOfMaps = (Map) parser.parseExpression("{txt:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}" ) .getValue(); System.out.println("Map<Map>: " + mapOfMaps); }
输出结果
1 2 map: {txt=Nikola, dob=10-July-1856} Map<Map>: {txt={first=Nikola, last=Tesla}, dob={day=10, month=July, year=1856}}
4. 数组 数组可以借助new构造方法来实现,通过下标ary[index]的方式访问数组中的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private void array () { ExpressionParser parser = new SpelExpressionParser(); int [] numbers1 = (int []) parser.parseExpression("new int[4]" ).getValue(); System.out.println("array: " + JSON.toJSONString(numbers1)); int [] numbers2 = (int []) parser.parseExpression("new int[]{1,2,3}" ).getValue(); System.out.println("array: " + JSON.toJSONString(numbers2)); int [][] numbers3 = (int [][]) parser.parseExpression("new int[4][5]" ).getValue(); System.out.println("array: " + JSON.toJSONString(numbers3)); int [] nums = new int []{1 , 3 , 5 }; EvaluationContext context = new StandardEvaluationContext(); context.setVariable("num" , nums); Integer numVal = parser.parseExpression("#num[1]" ).getValue(context, Integer.class); System.out.println("numVal in array: " + numVal); }
输出如下
1 2 3 4 array: [0,0,0,0] array: [1,2,3] array: [[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]] numVal in array: 3
5. 表达式 Spel支持一些Java语法中常规的比较判断,算数运算,三元表达式,类型判断,matches正则匹配等基表表达式
下面给出一些简单的实例
1 2 3 4 5 6 7 8 9 10 11 12 public void expression () { ExpressionParser parser = new SpelExpressionParser(); System.out.println("1+2= " + parser.parseExpression("1+2" ).getValue()); System.out.println("1<2= " + parser.parseExpression("1<2" ).getValue()); System.out.println("true ? hello : false > " + parser.parseExpression("3 > 2 ? 'hello': 'false' " ).getValue()); System.out.println("instance : " + parser.parseExpression("'a' instanceof T(String)" ).getValue()); System.out.println("22 是否为两位数字 :" + parser.parseExpression("22 matches '\\d{2}'" ).getValue()); }
输出结果
1 2 3 4 5 1+2= 3 1<2= true true ? hello : false > helloinstance : true 22 是否为两位数字 :true
6. Type与静态类 如果想获取Class对象,或者访问静态成员/方法,可以借助T()语法来实现
比如我们有一个静态类
1 2 3 4 5 6 7 public static class StaClz { public static String txt = "静态属性" ; public static String hello (String tag) { return txt + " : " + tag; } }
如果希望访问静态属性txt, 表达式可以写成T(com.git.hui.boot.spel.demo.BasicSpelDemo.StaClz).txt,请注意圆括号中的是完整签名;访问静态方法方式类似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void type () { ExpressionParser parser = new SpelExpressionParser(); String name = parser.parseExpression("T(com.git.hui.boot.spel.demo.BasicSpelDemo.StaClz).txt" ).getValue(String.class); System.out.println("txt: " + name); String methodReturn = parser.parseExpression("T(com.git.hui.boot.spel.demo.BasicSpelDemo.StaClz).hello" + "('一灰灰blog')" ) .getValue(String.class); System.out.println("static method return: " + methodReturn); Class stringClass = parser.parseExpression("T(String)" ).getValue(Class.class); System.out.println("class: " + stringClass.getName()); }
输出结果如下
1 2 3 txt: 静态属性 static method return : 静态属性 : 一灰灰blog class: java.lang.String
上面的写法,请重点看一下T(String),这里的String没有用完整的包路径,即直接位于java.lang包下的类,是可以省略掉完整包名的,就像我们平时写代码时,也不需要显示的加一个import java.lang.*
7. 构造方法 上面介绍array的时候,就介绍了使用new来创建数组对象,当然也可以直接构造其他的普通对象, 如我们新建一个测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static class Person { private String name; private int age; public Person (String name, int age) { this .name = name; this .age = age; } public String getName () { return name; } public int getAge () { return age; } @Override public String toString () { return "Person{" + "txt='" + name + '\'' + ", age=" + age + '}' ; } }
通过SpEl创建一个对象的实例
1 2 3 4 5 6 public void construct () { ExpressionParser parser = new SpelExpressionParser(); Person person = parser.parseExpression("new com.git.hui.boot.spel.demo.BasicSpelDemo.Person('一灰灰', 20)" ) .getValue(Person.class); System.out.println("person: " + person); }
输出结果如下:
1 person: Person{txt='一灰灰' , age=20}
请注意,构造方法中类的完整签名
8. 变量引用 细心的小伙伴,在上面介绍数组的成员演示的实例中,写法如"#num[1]",这个num前面有一个#,这是一个语法定义,有#修饰的表示变量访问
要理解这一小节,首先得理解EvaluationContext, 在我们的SpEL表达式的解析中,getValue有一个参数就是这个Context,你可以将他简单理解为包含一些对象的上下文,我们可以通过SpEL的语法,来访问操作Context中的某些成员、成员方法属性等
一般的操作过程如下:
context.setVariable("person", person); 向EvaluationContext中塞入成员变量
parser.parseExpression(xxx).getValue(context) 解析SpEL表达式,context必须作为传参丢进去哦
一个简单的实例
1 2 3 4 5 6 7 8 9 10 11 12 public void variable () { ExpressionParser parser = new SpelExpressionParser(); Person person = new Person("一灰灰blog" , 18 ); EvaluationContext context = new StandardEvaluationContext(); context.setVariable("person" , person); String name = parser.parseExpression("#person.getName()" ).getValue(context, String.class); System.out.println("variable name: " + name); Integer age = parser.parseExpression("#person.age" ).getValue(context, Integer.class); System.out.println("variable age: " + age); }
输出结果如下
1 2 variable name: 一灰灰blog variable age: 18
友情提示,如果访问对象的私有Field/method,会抛异常
9. 函数 Context中的变量,除了是我们常见的基本类型,普通的对象之外,还可以是方法,在setVariable时,设置的成员类型为method即可
1 2 3 4 5 6 7 8 9 10 11 12 13 public void function () { try { ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("hello" , StaClz.class.getDeclaredMethod("hello" , String.class)); String ans = parser.parseExpression("#hello('一灰灰')" ).getValue(context, String.class); System.out.println("function call: " + ans); } catch (Exception e) { e.printStackTrace(); } }
输出结果如下
1 function call: 静态属性 : 一灰灰
10. bean访问 在Spring中,什么对象最常见?当然是bean, 那么我们可以直接通过SpEL访问bean的属性、调用方法么?
要访问bean对象,所以我们的EvaluationContext中需要包含bean对象才行
借助BeanResolver来实现,如context.setBeanResolver(new BeanFactoryResolver(applicationContext));
其次访问bean的前缀修饰为@符号
为了演示这种场景,首先创建一个普通的Bean对象
1 2 3 4 5 6 7 8 9 10 11 12 @Data @Component public class BeanDemo { private String blog = "https://spring.hhui.top" ; private Integer num = 8 ; public String hello (String name) { return "hello " + name + ", welcome to my blog " + blog + ", now person: " + num; } }
接着我们需要获取ApplicationContext,所以可以稍微改一下我们的测试类,让它继承自ApplicationContextAware
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private ApplicationContext applicationContext;@Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { this .applicationContext = applicationContext; } public void bean () { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new BeanFactoryResolver(applicationContext)); BeanDemo beanDemo = parser.parseExpression("@beanDemo" ).getValue(context, BeanDemo.class); System.out.println("bean: " + beanDemo); String ans = parser.parseExpression("@beanDemo.hello('一灰灰blog')" ).getValue(context, String.class); System.out.println("bean method return: " + ans); }
上面的写法和之前的并没有太大的区别,实际输出结果如下
1 2 bean: BeanDemo(blog=https://spring.hhui.top, num=8) bean method return : hello 一灰灰blog, welcome to my blog https://spring.hhui.top, now person: 8
11. ifElse SpEL支持三元表达式,在上述的表达式中也给出了实例
1 2 3 4 5 6 public void ifThenElse () { ExpressionParser parser = new SpelExpressionParser(); String ans = parser.parseExpression("true ? '正确': '错误'" ).getValue(String.class); System.out.println("ifTheElse: " + ans); }
输出结果如下
12. elvis
xx != null ? xx : yy => xx?:yy
这个也属于我们经常遇到的一种场景,如果xx为null,则返回yy;否则直接返回xx;简化写法为elvis写法: xx?:yy
1 2 3 4 5 6 7 8 9 10 11 12 public void elvis () { ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = new StandardEvaluationContext(); context.setVariable("name" , null ); String name = parser.parseExpression("#name?:'Unknown'" ).getValue(context, String.class); System.out.println("elvis-before " + name); context.setVariable("name" , "Exists!" ); name = parser.parseExpression("#name?:'Unknown'" ).getValue(context, String.class); System.out.println("elvis-after " + name); }
输出结果如下
1 2 elvis-before Unknown elvis-after Exists!
13. 安全表达式 在java中,最常见最讨厌的是一个就是NPE的问题,SpEL中当然也可能出现这种情况,但是若在SpEL中进行非空判断,那就很不优雅了,SpEL提供了xx?.yy的写法来避免npe,即
xx == null ? null : xx.yy => xx?.yy
举例说明
1 2 3 4 5 6 7 8 9 10 11 12 public void safeOperate () { ExpressionParser parser = new SpelExpressionParser(); Person person = new Person(null , 18 ); String name = parser.parseExpression("name?.length()" ).getValue(person, String.class); System.out.println("safeOperate-before: " + name); person.name = "一灰灰blog" ; name = parser.parseExpression("name?.length()" ).getValue(person, String.class); System.out.println("safeOperate-after: " + name); }
输出结果如下
1 2 safeOperate-before: null safeOperate-after: 7
14. 容器截取 遍历容器,获取子集,相当于jdk8 Stream中filter用法,语法格式如下
xx.?[expression], 请注意中括弧中的表达式必须返回boolean
举例说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public void collectionSelection () { List<Integer> list = new ArrayList<>(Arrays.asList(1 , 3 , 4 , 6 , 7 , 8 , 9 )); ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("list" , list); List<Integer> subList = (List<Integer>) parser.parseExpression("#list.?[#this>5]" ).getValue(context); System.out.println("subList: " + subList); Map<String, Integer> map = new HashMap<>(); map.put("a" , 1 ); map.put("b" , 10 ); map.put("c" , 4 ); map.put("d" , 7 ); context.setVariable("map" , map); Map subMap = parser.parseExpression("#map.?[value < 5]" ).getValue(context, Map.class); System.out.println("subMap: " + subMap); subMap = parser.parseExpression("#map.?[key == 'a']" ).getValue(context, Map.class); System.out.println("subMap: " + subMap); }
输出结果如下
1 2 3 subList: [6, 7, 8, 9] subMap: {a=1, c=4} subMap: {a=1}
注意
在列表表达式中,可以通过#this来指代列表中的每一个元素
在map表达式中,通过key, value来分别指代map中的k,v
15. 容器映射 将一个集合通过某种规则,映射为另一种集合,相当于jdk8 Stream中的map用法,语法如下
xx.![expression], 将表达式计算的结果作为输出容器中的成员
举例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public void collectionProjection () { List<Integer> list = new ArrayList<>(Arrays.asList(1 , 3 , 4 , 6 , 7 , 8 , 9 )); ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("list" , list); List newList = parser.parseExpression("#list.![#this * 2]" ).getValue(context, List.class); System.out.println("newList: " + newList); Map<String, Integer> map = new HashMap<>(); map.put("a" , 1 ); map.put("b" , 10 ); map.put("c" , 4 ); map.put("d" , 7 ); context.setVariable("map" , map); List newListByMap = parser.parseExpression("#map.![value * 2]" ).getValue(context, List.class); System.out.println("newListByMap: " + newListByMap); }
输出结果如下:
1 2 newList: [2, 6, 8, 12, 14, 16, 18] newListByMap: [2, 20, 8, 14]
16. 表达式模板 SpEL还提供了一种自定义表达式模板的方式,将字面量和表达式放在一起使用,比如下面这一条语句
1 "random number is #{T(java.lang.Math).random()}"
其中#{T(java.lang.Math).random()}是一个SpEL表达式,左边的是普通字符串,这种写法也常见于@Value注解中的属性写法,当然直接通过上面的写法执行这个语句会报错,这个时候需要指定ParserContext
举例说明
1 2 3 4 5 6 7 public void template () { ExpressionParser parser = new SpelExpressionParser(); String randomPhrase = parser.parseExpression("random number is #{T(java.lang.Math).random()}" , ParserContext.TEMPLATE_EXPRESSION).getValue(String.class); System.out.println("template: " + randomPhrase); }
输出结果如下
1 template: random number is 0.10438946298113871
17. 小结 SpEL属于非常强大的表达式语言了,就我个人的感觉而言,它和OGNL有些像,当它们的上下文中包含了Spring的上下文时,可以访问任何的bean,而你可以借助它们的语法规范,做各种事情
推荐我之前的一个项目,https://github.com/liuyueyi/quick-fix,利用ognl结合ApplicationContext,可以随心所欲的访问控制应用中的任何bean对象
II. 其他 0. 项目
1. 一灰灰Blog 尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
打赏
如果觉得我的文章对您有帮助,请随意打赏。
微信打赏
支付宝打赏