简介
说明
本文介绍Spring的SpEL的用法。
Spring Expression Language (SpEL) 是强大的表达式语言,支持查询、操作对象图,以及解析逻辑、算术表达式。SpEL可以独立使用,无论你是否使用Spring框架。
SpEL能做什么?
SpEL一般用于注解,比如:日志注解、分布式锁注解、权限管理等等。
例1:@Value取值
@Value("#{systemProperties['file.encoding']}") private String encoding; @Value("#{systemEnvironment['JAVA_HOME']}") private String home;
例2:Spring Security权限管理框架
@RestController @RequestMapping("/admin/user") public class UserController { /** * 拥有管理员权限可查看任何用户信息,否则只能查看自己的信息 */ @PreAuthorize("hasAuthority('ROLE_DMIN') or #reqVo.sysUser.username == #userDetails.username") @PostMapping("/findUsers") public Result<List<SysUser>> findUsers(@RequestBody FindUsersReqVo reqVo, @AuthenticationPrincipal UserDetails userDetails) { PageInfo<SysUser> pageInfo = userService.findUsers(reqVo); return new Result<>(pageInfo.getList(), pageInfo.getTotal()); } }
实例
如果使用了Spring,默认就带了SpEL依赖。如果没用Spring,可以手动引入依赖:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>5.3.30</version> </dependency>
基本表达式
字面量
package com.knife.example.demo; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; public class Demo { public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); // 字符串 String str1 = parser.parseExpression("'test'").getValue(String.class); String str2 = parser.parseExpression("\"test\"").getValue(String.class); // 数字类型 int int1 = parser.parseExpression("1").getValue(Integer.class); long long1 = parser.parseExpression("1L").getValue(long.class); float float1 = parser.parseExpression("1.1").getValue(Float.class); double double1 = parser.parseExpression("1.1E+1").getValue(double.class); int hex1 = parser.parseExpression("0xf").getValue(Integer.class); // 布尔类型 boolean true1 = parser.parseExpression("true").getValue(boolean.class); // null类型 Object null1 = parser.parseExpression("null").getValue(Object.class); } }
结果如下
算数运算
SpEL支持:加(+)、减(-)、乘(*)、除(/)、求余(%)、幂(^)等算数运算。
并且支持用英文替代符号,如:MOD等价%、DIV等价/,且不区分大小写。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); int int1 = parser.parseExpression("3+2").getValue(Integer.class);// 5 int int2 = parser.parseExpression("3*2").getValue(Integer.class);// 6 int int3 = parser.parseExpression("3%2").getValue(Integer.class);// 1 int int4 = parser.parseExpression("3^2").getValue(Integer.class);// 9 }
关系运算表达式
SpEL支持:等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=),区间(between)等关系运算。
“EQ” 、“NE”、 “GT”、“GE”、 “LT” 、“LE”来表示等于、不等于、大于、大于等于、小于、小于等于
并且支持用英文替代符号,如:EQ等价==、NE等价!=、GT等价>、GE等价>=、LT等价<、LE等价<=,且不区分大小写。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); boolean b1 = parser.parseExpression("3==2").getValue(Boolean.class);// false boolean b2 = parser.parseExpression("3>=2").getValue(Boolean.class);// true boolean b3 = parser.parseExpression("2 between {2, 3}").getValue(Boolean.class);// true }
逻辑运算
SpEL支持:或(or)、且(and)、非(!或NOT)。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); boolean b1 = parser.parseExpression("2>1 and false").getValue(Boolean.class);// false boolean b2 = parser.parseExpression("2>1 or false").getValue(Boolean.class);// true boolean b3 = parser.parseExpression("NOT false and (2>1 and 3>1)").getValue(Boolean.class);// true }
字符串连接及截取
SpEL支持字符串拼接和字符串截取(目前只支持截取一个字符)。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); String str1 = parser.parseExpression("'hello' + ' java'").getValue(String.class);// hello java String str2 = parser.parseExpression("'hello java'[0]").getValue(String.class);// h String str3 = parser.parseExpression("'hello java'[1]").getValue(String.class);// e }
三目运算
SpEL支持三目运算。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); Boolean b1 = parser.parseExpression("3 > 2 ? true : false").getValue(Boolean.class);// true System.out.println(b1); }
Elivis
SpEL支持Elivis表达式。
Elivis运算符表达式格式:表达式1?:表达式2
Elivis运算符是从Groovy语言引入,用于简化三目运算符(表达式1? 表达式1:表达式2)的。当表达式1为非null时则返回表达式1,当表达式1为null时则返回表达式2
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); String str1 = parser.parseExpression("'a' ?: 'b'").getValue(String.class);// a String str2 = parser.parseExpression("null ?: 'b'").getValue(String.class);// b Boolean b1 = parser.parseExpression("3 > 2 ?: false").getValue(Boolean.class);// true Boolean b2 = parser.parseExpression("null ?: false").getValue(Boolean.class);// false Boolean b3 = parser.parseExpression("false ?: true").getValue(Boolean.class);// false }
正则表达式
SpEL支持正则表达式
具体使用格式:str matches regex,其中str表示需要校验的字符串,regex表示正则表达式。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); Boolean b1 = parser.parseExpression("'123' matches '\\d{3}'").getValue(Boolean.class);// true Boolean b2 = parser.parseExpression("'123' matches '\\d{2}'").getValue(Boolean.class);// false }
类相关表达式
类类型
SpEL支持使用T(Type)来表示java.lang.Class实例,Type必须是类全限定名(java.lang包下的类可以不指定包名)。使用类类型表达式还可以进行访问类静态方法及类静态字段。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); // java.lang包 Class class1 = parser.parseExpression("T(String)").getValue(Class.class); // 其他包 Class class2 = parser.parseExpression("T(java.util.Date)").getValue(Class.class); // 类静态字段访问 int result3 = parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class); // 类静态方法调用 int result4 = parser.parseExpression("T(Integer).parseInt('2')").getValue(int.class); }
类实例
SpEL支持类实例化,使用java关键字new,类名必须是全限定名(java.lang包内的类型除外,如String、Integer)。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); // java.lang包 String str = parser.parseExpression("new String('str')").getValue(String.class); // 其他包 Date date = parser.parseExpression("new java.util.Date()").getValue(Date.class); }
instanceof
SpEL支持instanceof运算符,跟Java内使用方法相同。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); Boolean b1 = parser.parseExpression("'haha' instanceof T(String)").getValue(Boolean.class);// true Boolean b2 = parser.parseExpression("123 instanceof T(String)").getValue(Boolean.class);// false Boolean b3 = parser.parseExpression("123 instanceof T(java.util.Date)").getValue(Boolean.class);// false }
变量定义及引用
SpEL支持:
- 使用#variableName引用通过EvaluationContext接口的setVariable(variableName, value)方法定义的变量;
- 使用#root引用根对象
- 使用#this引用当前上下文对象
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); // #variableName EvaluationContext context = new StandardEvaluationContext(); context.setVariable("name1", "value1"); String str1 = parser.parseExpression("#name1").getValue(context, String.class);// value1 User user = new User(); user.setId("1"); context = new StandardEvaluationContext(user); // #root(#root可以省略) String str2 = parser.parseExpression("#root.id").getValue(context, String.class);// 1 String str3 = parser.parseExpression("id").getValue(context, String.class);// 1 // #this String str4 = parser.parseExpression("#this.id").getValue(user, String.class);// 1 }
package com.knife.example.demo; import lombok.Data; @Data public class User { private String id; private Integer loginCount; }
操作属性
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); // #variableName EvaluationContext context; User user = new User(); user.setId("userId"); user.setLoginCount(43); context = new StandardEvaluationContext(user); // #root(#root可以省略) String str3 = parser.parseExpression("id + loginCount").getValue(context, String.class); // userId43 String str4 = parser.parseExpression("id + ',' + loginCount").getValue(context, String.class); // userId,43 String str5 = parser.parseExpression("id + \",,\" + loginCount").getValue(context, String.class); // userId,,43 }
赋值
SpEL支持给自定义变量赋值,也允许给根对象赋值,直接使用#variableName=value即可赋值。
- 使用#variable=value给自定义变量赋值
- 使用#root=value给根对象赋值
- 使用#this=value给当前上下文对象赋值
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); // #variableName EvaluationContext context = new StandardEvaluationContext(); User user = new User(); user.setId("1"); context.setVariable("name1", user); String str1 = parser.parseExpression("#name1.id='2'").getValue(context, String.class);// 2 context = new StandardEvaluationContext(user); // #root(#root可以省略) String str2 = parser.parseExpression("#root.id='3'").getValue(context, String.class);// 3 String str3 = parser.parseExpression("id='4'").getValue(context, String.class);// 4 // #this String str4 = parser.parseExpression("#this.id='5'").getValue(user, String.class);// 5 }
通过参数名字操作
可以通过指定参数名字来获取值。
目标:取得Test类里的test方法的orderNo入参的值。
Test类
package com.knife.example.demo; import com.knife.example.entity.User; class Test { public void test(String orderNo, User user) { } }
主方法
package com.knife.example.demo; import com.knife.example.entity.User; import org.springframework.context.expression.MethodBasedEvaluationContext; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; public class Demo { private static String[] definitionKeys = {"#orderNo"}; public static void main(String[] args) { ExpressionParser PARSER = new SpelExpressionParser(); ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); String orderNo = "1234"; User user = new User(); user.setId("userId"); user.setLoginCount(43); Method method = null; try { method = Test.class.getMethod("test", String.class, User.class); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } // 构造入参 Object[] parameters = new Object[] {orderNo, user}; EvaluationContext context = new MethodBasedEvaluationContext(null, method, parameters, NAME_DISCOVERER); List<String> definitionValueList = new ArrayList<>(definitionKeys.length); for (String definitionKey : definitionKeys) { if (definitionKey != null && !definitionKey.isEmpty()) { String key = PARSER.parseExpression(definitionKey).getValue(context, String.class); definitionValueList.add(key); } } // 打印所有的key对应的value System.out.println(definitionValueList); // 调用方法 try { method.invoke(new Test(), parameters); } catch (Exception e) { throw new RuntimeException(e); } } }
结果
[1234]
自定义函数
SpEL支持类静态方法注册为自定义函数。SpEL使用StandardEvaluationContext的registerFunction方法进行注册自定义函数(等同于使用setVariable)。
public static void main(String[] args) throws NoSuchMethodException { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); Method parseInt = Integer.class.getDeclaredMethod("valueOf", String.class); context.registerFunction("readValue", parseInt); // 等同于Integer.valueOf("2").byteValue() Byte b = parser.parseExpression("#readValue('2').byteValue()").getValue(context, Byte.class);// 2 }
对象属性获取及安全导航
SpEL支持:
- 对象属性获取:使用如object.property.property这种点缀式获取
- 安全导航:SpEL引入了Groovy语言中的安全导航运算符”(对象|属性)?.属性”,用来避免”?.”前边的表达式为null时抛出空指针异常,而是返回null;修改对象属性值则可以通过赋值表达式或Expression接口的setValue方法修改。
注意:
- SpEL对于属性名首字母是不区分大小写的。
- 安全导航运算符前面的#root可以省略,但后面的#root不可省略。
public static void main(String[] args) throws NoSuchMethodException { ExpressionParser parser = new SpelExpressionParser(); User user = new User(); user.setId("1"); StandardEvaluationContext context = new StandardEvaluationContext(user); // 对象属性获取 String str1 = parser.parseExpression("id").getValue(context, String.class);// 1 String str2 = parser.parseExpression("Id").getValue(context, String.class);// 1 // 安全导航 User user1 = parser.parseExpression("#root?.#root").getValue(context, User.class);// {"id":"1"} String str3 = parser.parseExpression("id?.#root.id").getValue(context, String.class);// 1 String str4 = parser.parseExpression("userName?.#root.userName").getValue(context, String.class);// null }
对象方法调用
SpEL支持对象方法调用,使用方法跟Java语法一样。对于根对象可以直接调用方法。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext("user"); // 对象方法调用 String str1 = parser.parseExpression("#root.substring(1, 2)").getValue(context, String.class);// s String str2 = parser.parseExpression("substring(1, 2)").getValue(context, String.class);// 1 }
Bean引用
SpEL支持使用@符号来引用Bean,在引用Bean时需要使用BeanResolver接口实现来查找Bean,Spring提供BeanFactoryResolver实现。
@Autowired private ApplicationContext applicationContext; @GetMapping("test1") public ApiResult test1() { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new BeanFactoryResolver(applicationContext)); // 获取Spring容器中beanName为systemProperties的bean Properties systemProperties = parser.parseExpression("@systemProperties").getValue(context, Properties.class); System.out.println(JSON.toJSONString(systemProperties)); // RedisProperties redisProperties = parser.parseExpression("@spring.redis-org.springframework.boot.autoconfigure.data.redis.RedisProperties").getValue(context, RedisProperties.class); // System.out.println(JSON.toJSONString(redisProperties)); return ApiUtil.success(); }
注意:特殊的bean名称直接使用@beanName会报错。
比如RedisProperties,它的beanName为spring.redis-org.springframework.boot.autoconfigure.data.redis.RedisProperties,会误解析为:
- Spring容器的beanName为spring
- redis-org、springframework、boot、autoconfigure、data、redis、RedisProperties为它的一层层的属性。
这种具体怎么处理暂未研究。
集合相关表达式
注意:SpEL不支持内联字典(Map)定义。
内联数组定义
SpEL支持内联数组(Array)定义(一维或者多维)。
- 支持一维数组定义并初始化
- 不支持多维数组定义并初始化。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); // 定义一维数组并初始化 int[] array1 = parser.parseExpression("new int[2]{1,2}").getValue(int[].class); // 定义二维数组但不初始化 String[][] array2 = parser.parseExpression("new String[2][2]").getValue(String[][].class); // 定义二维数组并初始化(会报错) String[][] array3 = parser.parseExpression("new String[2][2]{{'1','2'},{'3','4'}}").getValue(String[][].class); }
内联集合定义
SpEL支持内联集合(List)定义。使用{表达式,……}定义内联List,如{1,2,3}将返回一个整型的ArrayList,而{}将返回空的List。
对于字面量表达式列表,SpEL会使用java.util.Collections.unmodifiableList方法将列表设置为不可修改。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); // 将返回不可修改的空List List<Integer> list1= parser.parseExpression("{}").getValue(List.class); // 对于字面量列表也将返回不可修改的List List<Integer> list2 = parser.parseExpression("{1,2,3}").getValue(List.class); //对于列表中只要有一个不是字面量表达式,将只返回原始List(可修改) String expression3 = "{{1+2,2+4},{3,4+4}}"; List<List<Integer>> list3 = parser.parseExpression(expression3).getValue(List.class); list3.get(0).set(0, 1); }
数组、集合、Map元素访问
SpEL支持集合、Map元素访问。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); // 数组元素访问 int[] array1 = {1,2,3}; Integer int1 = parser.parseExpression("[0]").getValue(array1, int.class); // 集合元素访问 Integer int2 = parser.parseExpression("{1,2,3}[0]").getValue(int.class); List<Integer> list1 = Stream.of(1, 2, 3).collect(Collectors.toList()); Integer int3 = parser.parseExpression("[0]").getValue(list1, Integer.class); // Map元素访问 Map<String, Integer> map = new HashMap<>(); map.put("a", 1); map.put("b", 2); map.put("c", 3); Integer int4 = parser.parseExpression("['b']").getValue(map, Integer.class); }
结果
集合、Map嵌套元素访问
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); // List里是Map Map<String, Object> map = new HashMap<>(); map.put("a", 1); map.put("b", 2); map.put("c", 3); List<Map> list = new ArrayList<>(); list.add(map); Integer int1 = parser.parseExpression("[0]['b']").getValue(list, Integer.class); // Map嵌套访问 Map<String, Object> map1 = new HashMap<>(); map1.put("a", 1); map1.put("b", 2); map1.put("c", 3); Map<String, Object> map2 = new HashMap<>(); map2.put("d", map1); Integer int2 = parser.parseExpression("['d']['a']").getValue(map2, Integer.class); }
结果
数组、集合、Map元素修改
SpEL支持数组、集合、Map元素修改。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); // 数组元素修改 int[] array = new int[] {1, 2, 3}; parser.parseExpression("[0] = 3").getValue(array, int.class); // 集合元素修改 List<Integer> list = Stream.of(1, 2, 3).collect(Collectors.toList()); parser.parseExpression("[0] = 3").getValue(list, Integer.class); // 字典元素修改 Map<String, Integer> map = new HashMap<>(); map.put("a", 1); map.put("b", 2); map.put("c", 3); parser.parseExpression("['b'] = 3").getValue(map, Integer.class); }
数组、集合、Map投影
SpEL支持数组、集合、Map投影。SpEL根据原集合中的元素中通过选择来构造另一个集合,该集合和原集合具有相同数量的元素。数组和集合类似,Map构造后是集合(不是Map)。
SpEL使用list|map.![投影表达式]来进行投影运算。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); // 数组投影(#this可省略) int[] array = new int[] {1, 2, 3}; int[] array1 = parser.parseExpression("#root.![#this+1]").getValue(array, int[].class); // [2,3,4] // 集合投影(#this可省略) List<Integer> list = Stream.of(1, 2, 3).collect(Collectors.toList()); List list1 = parser.parseExpression("#root.![#this+1]").getValue(list, List.class); // [2,3,4] // Map投影(#this可省略) Map<String, Integer> map = new HashMap<>(); map.put("a", 1); map.put("b", 2); map.put("c", 3); List list2 = parser.parseExpression("#root.![#this.key+1]").getValue(map, List.class); // ["a1", "b1", "c1"] List list3 = parser.parseExpression("#root.![#this.value+1]").getValue(map, List.class); // [2,3,4' }
数组、集合、Map选择
SpEL支持数组、集合、Map选择。SpEL根据原集合通过条件表达式选择出满足条件的元素并构造为新的集合。数组和Map类似。
SpEL使用“(list|map).?[选择表达式]”,其中选择表达式结果必须是boolean类型,如果true则选择的元素将添加到新集合中,false将不添加到新集合中。
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); // 数组选择 int[] array = new int[] {1, 2, 3}; int[] array1 = parser.parseExpression("#root.?[#this>1]").getValue(array, int[].class); // [2,3] // 集合选择 List<Integer> list = Stream.of(1, 2, 3).collect(Collectors.toList()); List list1 = parser.parseExpression("#root.?[#this>1]").getValue(list, List.class); // [2,3 // Map选择 Map<String, Integer> map = new HashMap<>(); map.put("a", 1); map.put("b", 2); map.put("c", 3); Map map1 = parser.parseExpression("#root.?[#this.key!='a']").getValue(map, Map.class); // 只保留key不为a的元素 Map map2 = parser.parseExpression("#root.?[#this.value>1]").getValue(map, Map.class); // 只保留value大于1的元素 }
模板表达式
SpEL支持模板表达式。模板表达式由字面量与一个或多个表达式块组成。
表达式块由:”前缀+表达式+后缀”形式组成。
SpEL使用ParserContext接口实现来定义表达式是否是模板及前缀和后缀定义。
常见的前缀后缀:#{}、${}
模板表达式举例:
- “${1+2}”:前缀${、后缀}、表达式1+2。
- “Error ${#v0} ${#v1}”:字面量Error 、前缀${、后缀}、表达式#v0、表达式#v1,其中v0和v1表示自定义变量,需要在上下文定义。
#{}的使用
SpEL模板表达式
- MyBatis中的占位符,以预编译的方式传入参数,可以有效的防止SQL注入。
- 向占位符输入参数,MyBatis会自动进行Java类型jdbc类型的转换,且不需要考虑参数的类型,例如:传入字符串,MyBatis最终拼接好的SQL就是参数两边加单引号。
${}的使用
SpEL模板表达式
- 作用于@Value等注解的属性,用于获取配置文件的配置值
- MyBatis的SQL拼接,将参数的内容字符串替换方式拼接在SQL中,可能存在SQL注入的安全隐患。
- 在用于传入数据库对象,例如传入表名和列名,还有排序时使用order by动态参数时可以使用 ${ } 。
请先
!