找回密码
 立即注册
首页 业界区 业界 Spring使用el表达式

Spring使用el表达式

琉艺戕 7 天前
Spring使用el表达式

0. 背景

在项目中需要一个可动态执表达式的功能.项目本身是基于Springboot,可直接使用Spring提供的核心模块Spring Expression Language(SpEL). 在测试前,需要了解一些基本前置条件

  • 整体可以按照java语法编写脚本串
  • 只支持单条语句执行
  • 表达式串中,内部字符串可以使用单引号,双引号(需要转义)
  • 表达默认导入的包为java.lang.*,如果是其他包下的对象,需要使用包名和类名详细可以查看类StandardTypeLocator对应的构造函数
以下 1 到 10 章节为每个语法的测试代码以及执行结果.如果对测试代码和结果不敢兴趣可以快速移动到文末总结部分.对所有语法的一个简单汇总.这里只说明基本使用,下一篇会讲一些安全相关的防止RCE攻击.以及一些扩展能力
1. 调用静态方法

语法:T(class),比如T(Runtime)就是获取到类Runtime.分别需要测试测试java.lang.* 和其他包下的对象调用方式
  1.         public void testExecClassMethod() {
  2.     String classMethodExpr = "T(Runtime).getRuntime().exec('calc')";
  3.     ExpressionParser elParser = new SpelExpressionParser();
  4.     Object value = elParser.parseExpression(classMethodExpr).getValue();
  5.     System.out.println("(  java.lang)执行类方法T(class): " + value);
  6.     // 需要 包名+类名
  7.     String otherJarMethodExpr = "T(org.apache.commons.lang.StringEscapeUtils).escapeHtml('小游戏 地心侠士')";
  8.     Object thridJarValue = elParser.parseExpression(otherJarMethodExpr).getValue();
  9.     System.out.println("(非java.lang)执行类方法T(class): " + thridJarValue);
  10.         }
复制代码
以上代码执行结果如下
  1. (  java.lang)执行类方法T(class): Process[pid=31572, exitValue="not exited"]
  2. (非java.lang)执行类方法T(class): 小游戏 地心侠士
复制代码
从执行结果可知道,动态执行脚本是很危险的操作,一部小心就被别人利用,导致RCE攻击.比如上边第一个测试代码轻松调用计算器功能.
2. 调用实例方法

语法:#root.method(args) 或者#变量名.method(args)或者直接methodName(args)
  1. public void testInstanceMethod() {
  2.   String elExpr = "substring(4,8)+' 小游戏 '+(333+333)";
  3.   String elLengthExpr = "#root.length()";
  4.   String testStr = "小游戏 地心侠士";
  5.   System.out.println("===测试字符串===");
  6.   System.out.println(testStr);
  7.   ExpressionParser elParser = new SpelExpressionParser();
  8.   Object strLen = elParser.parseExpression(elLengthExpr).getValue(testStr);
  9.   Object value = elParser.parseExpression(elExpr).getValue(testStr);
  10.   System.out.println("===使用getValue传入具体的对象==");
  11.   System.out.println(elLengthExpr + ": " + strLen);
  12.   System.out.println(elExpr + ":" + value);
  13.   StandardEvaluationContext ctx = new StandardEvaluationContext();
  14.   ctx.setRootObject(testStr);
  15.   ctx.setVariable("var", testStr);
  16.   value = elParser.parseExpression(elExpr).getValue(ctx);
  17.   strLen = elParser.parseExpression(elLengthExpr).getValue(ctx);
  18.   Object varVal = elParser.parseExpression("#var." + elExpr).getValue(ctx);
  19.   System.out.println("===使用ctx传入变量和root对象==");
  20.   System.out.println(elLengthExpr + " :  " + strLen);
  21.   System.out.println(elExpr + " : " + value);
  22.   System.out.println("#var." + elExpr + " : " + varVal);
  23. }
复制代码
以上代码执行结果如下:
  1. ===测试字符串===
  2. 小游戏 地心侠士
  3. ===使用getValue传入具体的对象==
  4. #root.length(): 8
  5. substring(4,8)+' 小游戏 '+(333+333):地心侠士 小游戏 666
  6. ===使用ctx传入变量和root对象==
  7. #root.length() :  8
  8. substring(4,8)+' 小游戏 '+(333+333) : 地心侠士 小游戏 666
  9. #var.substring(4,8)+' 小游戏 '+(333+333) : 地心侠士 小游戏 666
复制代码
从执行结果可以知道,在传递具体实例对象是,就是调用 setRootObject ,针对根对象,可以使用#root.methodName(args)也可以直接调用方法 methodName(args)
3. 调用构造函数

语法:new className() 测试 String 构造函数,日期格式化构造函数同java日期对象结合,已经一个简单pojo对象
  1. public void testInstanceCtor() {
  2.   String strCtor = "new String('小游戏 地心侠士')";
  3.   ExpressionParser elParser = new SpelExpressionParser();
  4.   Object strVal = elParser.parseExpression(strCtor).getValue();
  5.   System.out.println(strCtor + ": " + strVal);
  6.   String cotrExpr = "new java.text.SimpleDateFormat('yyyy-MM-dd HH:mm:ss')";
  7.   SimpleDateFormat df = (SimpleDateFormat) elParser.parseExpression(cotrExpr).getValue();
  8.   System.out.println("当前时间: " + df.format(new Date()));
  9.   String innerEl = "new com.herbert.script.SpringElScript.ElTestObject('小游戏','地心侠士')";
  10.   ElTestObject value = elParser.parseExpression(innerEl).getValue(ElTestObject.class);
  11.   System.out.println(value);
  12.   ElTestObject javaObj = new ElTestObject("小游戏", "地心侠士");
  13.   Object gameName = elParser.parseExpression("gameName").getValue(javaObj);
  14.   System.out.println("获取java对象上的值:" + gameName);
  15. }
复制代码
以上代码,执行结果如下
  1. new String('小游戏 地心侠士'): 小游戏 地心侠士
  2. 当前时间: 2025-11-29 21:54:55
  3. ElTestObject [gameType=小游戏, gameName=地心侠士]
  4. 获取java对象上的值:地心侠士
复制代码
从这是结果可以知道getValue初始的后的对象,已经就是java对象,可以直接获取对象属性.两者之间,可以相互穿插调用.
针对 Map 和 List 构造函数,也提供了一种内联的方式初始方式
Map 语法:{key:value}
List 语法:{value1,value2,value3}
  1. public void testInlineMapAndList() {
  2.   String initMap = "{'gameType':'小游戏','gameName':'地心侠士'}";
  3.   String initList = "{'小游戏','地心侠士'}";
  4.   ExpressionParser elParser = new SpelExpressionParser();
  5.   Map map = elParser.parseExpression(initMap).getValue(Map.class);
  6.   System.out.println("===通过{key:value}初始的内容===");
  7.   System.out.println(String.format("%s : %s ", map.get("gameType"), map.get("gameName")));
  8.   List lst = elParser.parseExpression(initList).getValue(List.class);
  9.   System.out.println("===通过{value,value}初始的内容===");
  10.   System.out.println(String.format("%s : %s ", lst.get(0), lst.get(1)));
  11. }
复制代码
执行结果如下:
  1. ===通过{key:value}初始的内容===
  2. 小游戏 : 地心侠士
  3. ===通过{value,value}初始的内容===
  4. 小游戏 : 地心侠士
复制代码
4. 获取SpringBean

语法beanName,如果不在Spring环境,需要实现一个BeanResolver接口,这里只测试Spring环境中.
在Springboot中注册一个bean
  1. @Bean
  2. public Map elMap() {
  3.   Map elMap = new HashMap();
  4.   elMap.put("gameType", "小游戏");
  5.   elMap.put("gameName", "地心侠士");
  6.   return elMap;
  7. }
复制代码
测试代码如下,这个特别地方需要使用BeanFactoryResolver包装 applicationContext 放到context中
  1. @Autowired
  2. ApplicationContext applicationContext;
  3. public void testSpringBean() {
  4.   Consumer<Map> printMap = map -> {
  5.           System.out.println(map.get("gameType") + ": " + map.get("gameName"));
  6.   };
  7.   System.out.println("=====使用 Autowired 获取到bean内容=======");
  8.   printMap.accept(elMap);
  9.   Map ctxElMap = (Map) applicationContext.getBean("elMap");
  10.   System.out.println("=====使用 applicationContext 获取到bean内容=======");
  11.   printMap.accept(ctxElMap);
  12.   String beanStr = "@elMap";
  13.   ExpressionParser elParser = new SpelExpressionParser();
  14.   StandardEvaluationContext elContext = new StandardEvaluationContext();
  15.   // 注册spring BeanResolver
  16.   elContext.setBeanResolver(new BeanFactoryResolver((BeanFactory) applicationContext));
  17.   Map convertMap = (Map) elParser.parseExpression(beanStr).getValue(elContext);
  18.   System.out.println("=====使用 el 获取到bean内容(BeanResolver)=======");
  19.   printMap.accept(convertMap);
  20. }
复制代码
执行结果如下:
  1. =====使用 Autowired 获取到bean内容=======
  2. 小游戏: 地心侠士
  3. =====使用 applicationContext 获取到bean内容=======
  4. 小游戏: 地心侠士
  5. =====使用 el 获取到bean内容(BeanResolver)=======
  6. 小游戏: 地心侠士
复制代码
5. 获取List元素

语法:list[index]
  1. public void testGetListValue() {
  2.   ArrayList<String> datas = new ArrayList<>();
  3.   datas.add("小游戏");
  4.   datas.add("地心侠士");
  5.   String listEL = "'使用Variable '+#ary[0] +' :  '+#ary[1]";
  6.   System.out.println("==SPEL 获取LIST采用 对象[index] 方式==");
  7.   ExpressionParser elParser = new SpelExpressionParser();
  8.   StandardEvaluationContext elContext = new StandardEvaluationContext();
  9.   elContext.setVariable("ary", datas);
  10.   Object value = elParser.parseExpression(listEL).getValue(elContext);
  11.   System.out.println(value);
  12.   // 测试rootObje
  13.   elContext.setRootObject(datas);
  14.   listEL = "'使用rootObjet: '+#root[0] + ' : '+#root[1]";
  15.   value = elParser.parseExpression(listEL).getValue(elContext);
  16.   System.out.println(value);
  17.   System.out.println("数组'[1]'获取到的值:"+elParser.parseExpression("[1]").getValue(elContext));
  18. }
复制代码
执行结果如下:
  1. ==SPEL 获取LIST采用 对象[index] 方式==
  2. 使用Variable 小游戏 :  地心侠士
  3. 使用rootObjet: 小游戏 : 地心侠士
  4. 数组'[1]'获取到的值:地心侠士
复制代码
6. 获取Map元素

语法:map[key]或者map.get(key)
  1. public void testGetMapEntryValue() {
  2.   String beanStr = "@elMap.get('gameType')+' : ' + @elMap.get('gameName')";
  3.   ExpressionParser elParser = new SpelExpressionParser();
  4.   StandardEvaluationContext elContext = new StandardEvaluationContext();
  5.   elContext.setBeanResolver(new BeanFactoryResolver((BeanFactory) applicationContext));
  6.   Object mapvalue = elParser.parseExpression(beanStr).getValue(elContext);
  7.   System.out.println("===SPEL 获取Map采用 对象.get('mapkey') 方式===");
  8.   System.out.println(mapvalue);
  9.   mapvalue = elParser.parseExpression("@elMap['gameType']+' : '+@elMap['gameName']").getValue(elContext);
  10.   System.out.println("===SPEL 获取Map采用 对象['mapkey'] 方式===");
  11.   System.out.println(mapvalue);
  12.   }
复制代码
执行结果如下:
  1. ===SPEL 获取Map采用 对象.get('mapkey') 方式===
  2. 小游戏 : 地心侠士
  3. ===SPEL 获取Map采用 对象['mapkey'] 方式===
  4. 小游戏 : 地心侠士
复制代码
7. 集合筛选

语法:

  • list.?[condition] 过滤集合
  • list.^[condition] 返回过滤后的第一个
  • list.$[condition] 返回过滤后的最后一个
  • list.![condition] 投影,类似flatmap,会改变筛选集合类型
  1. public void testCollectionFilterAndFlatMap() {
  2.   List datas = Arrays.asList(new String[] { "小游戏", "地心侠士1", "地心侠士2" });
  3.   Map mapData = new HashMap() {
  4.           {
  5.                   put("gameType", "小游戏");
  6.                   put("gameName", "地心侠士");
  7.           }
  8.   };
  9.   String el = "#root.?[#this.contains('地心侠士')]";
  10.   System.out.println("===筛选List===");
  11.   ExpressionParser elParser = new SpelExpressionParser();
  12.   List filterList = elParser.parseExpression(el).getValue(datas, List.class);
  13.   filterList.forEach(System.out::println);
  14.   System.out.println("===list快速获取第一个===");
  15.   el = "#root.^[#this.contains('地心侠士')]";
  16.   Object value = elParser.parseExpression(el).getValue(datas);
  17.   System.out.println(value);
  18.   System.out.println("===list快速获取最后一个===");
  19.   el = "#root.$[#this.contains('地心侠士')]";
  20.   value = elParser.parseExpression(el).getValue(datas);
  21.   System.out.println(value);
  22.   System.out.println("===筛选Map按key筛选===");
  23.   el = "#root.?[key=='gameName']";
  24.   Map filterMap = elParser.parseExpression(el).getValue(mapData, Map.class);
  25.   filterMap.keySet().forEach(System.out::println);
  26.   System.out.println("===筛选Map按value筛选===");
  27.   el = "#root.?[value=='地心侠士']";
  28.   filterMap = elParser.parseExpression(el).getValue(mapData, Map.class);
  29.   filterMap.keySet().forEach(System.out::println);
  30.   System.out.println("===提取Map所有Value===");
  31.   el = "#root.![value]";
  32.   List flatMapList = elParser.parseExpression(el).getValue(mapData, List.class);
  33.   flatMapList.forEach(System.out::println);
  34. }
复制代码
执行结果如下:
  1. ===筛选List===
  2. 地心侠士1
  3. 地心侠士2
  4. ===list快速获取第一个===
  5. 地心侠士1
  6. ===list快速获取最后一个===
  7. 地心侠士2
  8. ===筛选Map按key筛选===
  9. gameName
  10. ===筛选Map按value筛选===
  11. gameName
  12. ===提取Map所有Value===
  13. 小游戏
  14. 地心侠士
复制代码
8. 实例赋值

语法: = 赋值 或者调用 Expression#setValue
  1. public void testAssignmentValue() {
  2.   ElTestObject obj = new ElTestObject();
  3.   String elExpr = "gameName";
  4.   ExpressionParser elParser = new SpelExpressionParser();
  5.   elParser.parseExpression(elExpr).setValue(obj, "地心侠士");
  6.   System.out.println("使用EL setValue :" + obj.getGameName());
  7.         
  8.   elExpr="gameName=#change";
  9.   StandardEvaluationContext ctx = new StandardEvaluationContext(obj);
  10.   ctx.setVariable("change", "小游戏");
  11.   elParser.parseExpression(elExpr).getValue(ctx);
  12.   System.out.println("使用EL = 赋值:" +obj.getGameName());
  13. }
复制代码
执行结果如下:
  1. 使用EL setValue :地心侠士
  2. 使用EL = 赋值:小游戏
复制代码
9. 特殊语法

特殊语法为EL特有,在标准java中不支持的语法
9.1 空判断语法 ?:'a',如果值为空则返回'a'
  1. public void testNullShort() {
  2. String nullStr = "null?:'空值出现'";
  3. ExpressionParser elParser = new SpelExpressionParser();
  4. Object value = elParser.parseExpression(nullStr).getValue();
  5. System.out.println("?: 空值表达式值: " + value);
  6. }
复制代码
执行结果如下:
  1. ?: 空值表达式值: 空值出现
复制代码
9.2 安全导航操作符?.aa,如果属性值aa不为空返回aa否则返回null
  1. public void testSafeNav() {
  2.           ElTestObject t = new ElTestObject();
  3.           t.setGameName("地心侠士");
  4.           String el = "gameName?.substring(0,2)";
  5.           ExpressionParser elParser = new SpelExpressionParser();
  6.           System.out.println("至空前运行结果: "+ elParser.parseExpression(el).getValue(t));
  7.           t.setGameName(null);
  8.           System.out.println("至空后运行结果: "+ elParser.parseExpression(el).getValue(t));
  9.           try {
  10.                   System.out.println("至空后不使用结果: "+ elParser.parseExpression("gameName.substring(0,1)").getValue(t));
  11.           } catch (Exception e) {
  12.                   System.out.println("未使用?.表达式,出现NPE异常");
  13.           }               
  14.   }
复制代码
执行结果如下:
  1. 至空前运行结果: 地心
  2. 至空后运行结果: null
  3. 未使用?.表达式,出现NPE异常
复制代码
9.3 属性可以使用索引方式访问,特殊如String[0],可以返回第一个字符,类似charAt(index)
  1. public void testIndexAccess() {
  2.   ElTestObject obj = new ElTestObject("小游戏", "地心侠士");
  3.   String el = "['gameName']";
  4.   ExpressionParser elParser = new SpelExpressionParser();
  5.   System.out.println("===使用[]访问属性===");
  6.   Object value = elParser.parseExpression(el).getValue(obj);
  7.   System.out.println(value);
  8.   System.out.println("===使用[]访问String===");
  9.   el = "[1]+#root.length";
  10.   value = elParser.parseExpression(el).getValue("地心侠士");
  11.   System.out.println("el[1]: "+value);
  12.   System.out.println("charAt(1): "+"地心侠士".charAt(1));
  13. }
复制代码
执行结果如下:
  1. ===使用[]访问属性===
  2. 地心侠士
  3. ===使用[]访问String===
  4. el[1]: 心4
  5. charAt(1): 心
复制代码
9.4 字符串支持 * - 操作符,但是特别注意-只支持单字符串.
  1. public void testStringOperator() {
  2.   String el = "'地心侠士 '*2";
  3.   ExpressionParser elParser = new SpelExpressionParser();
  4.   Object value = elParser.parseExpression(el).getValue();
  5.   System.out.println("===*操作符效果===");
  6.   System.out.println(value);
  7.   el = "'c'-2";
  8.   value = elParser.parseExpression(el).getValue();
  9.   System.out.println("===-操作符效果===");
  10.   System.out.println(value);
  11. }
复制代码
执行结果如下:
  1. ===*操作符效果===
  2. 地心侠士 地心侠士
  3. ===-操作符效果===
  4. a
复制代码
9.5 操作符 <  >= == != 同 lt le gt ge eq ne; && || ! 同 and or not; / %同 div mod 这三组都是等价的; 在使用中需要前后添加一个空格
  1. public void testSameOperator() {
  2.   String el = "1 gt 2";
  3.   ExpressionParser elParser = new SpelExpressionParser();
  4.   Object value = elParser.parseExpression(el).getValue();
  5.   System.out.println("=== > 换成 gt ===");
  6.   System.out.println(value);
  7.   el = "2 >1 and 2>2";
  8.   value = elParser.parseExpression(el).getValue();
  9.   System.out.println("=== && 换 and ===");
  10.   System.out.println(value);
  11.   el = "4 div 2";
  12.   value = elParser.parseExpression(el).getValue();
  13.   System.out.println("=== / 换 div ===");
  14.   System.out.println(value);
  15. }
复制代码
执行结果如下:
  1. === > 换成 gt ===
  2. false
  3. === && 换 and ===
  4. false
  5. === / 换 div ===
  6. 2
复制代码
9.6 操作符支持 between 和 正则 matches
  1. public void  testbetweenMatch() {
  2.   String el = "'b' between {'a','c'}";
  3.   ExpressionParser elParser = new SpelExpressionParser();
  4.   Object value = elParser.parseExpression(el).getValue();
  5.   System.out.println("===b 是否在 ac 之间===");
  6.   System.out.println(value);
  7.   el="'123' matches '^\\d{3}$'";
  8.   value = elParser.parseExpression(el).getValue();
  9.   System.out.println("===matches 检测三位数字 ===");
  10.   System.out.println(value);
  11. }
复制代码
输出结果如下:
  1. ===b 是否在 ac 之间===
  2. true
  3. ===matches 检测三位数字 ===
  4. true
复制代码
10. 总结

语法汇总:

  • T(className) 获取class对象
  • new className(xx,xx) 创建对象
  • @beanName 获取bean对象
  • {value1,value2} 简便创建数组
  • {'key1':'value'} 简便创建map
  • #root获取根对象#variable获取变量#this获取当前对象,集合筛选中使用
  • method() 或者 #variable.method 调用方法
  • property 或者 #variable.property 获取属性
  • [index]或者[propertyName]获取集合元素或对象属性
  • list.?[condition]筛选集合元素
  • list.?^[condition]返回筛选后的第一个元素
  • list.?$[condition]返回筛选后的最后一个元素
  • list.![propertyName]集合投影
  • ?:'value'空值判类似if(value==null){value='value'}
  • property?.method() 安全导航操作符,如果属性值不为空则返回属性值,否则返回null
  • setValue 或者 '=' 属性赋值
  • + - * / % = == != && || ! between matches instanceof 操作符,支持等价的转义符:lt le gt ge eq ne and or not div mod
  • string * number 表述复制nubmer个string
以上是基本的取值,赋值,筛选的基本操作语法.下期分享安全和扩展,如果需要完整测试代码,请在微信公众号:小满小慢 回复spel获取完整测试代码.
原文地址: https://mp.weixin.qq.com/s/6nXCJjrX2zAudd4KpFkANQ

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册