项目概况

第05篇:SpEL强大的表达式语言

2026-01-298 min read后端技术
Spring Framework

公众号: 西魏陶渊明
CSDN: https://springlearn.blog.csdn.net

天下代码一大抄, 抄来抄去有提高, 看你会抄不会抄!

一、概述

Spring 表达式语言(简称“SpEL”)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于 Unified EL,但提供了额外的功能,最值得注意的是方法调用和基本的字符串模板功能。

虽然还有其他几种可用的 Java 表达式语言——OGNL、MVEL 和 JBoss EL 等等

但创建 Spring 表达式语言的目的是为 Spring 社区提供一种可在所有产品中使用的受良好支持的表达式语言。它的语言特性由 Spring 产品组合中的项目需求驱动。

二、作用

2.1 基本表达式

字面量表达式、关系,逻辑与算数运算表达式、字符串连接及截取表达式、三目运算、正则表达式、括号优先级表达式;

2.2 类相关表达式

类类型表达式、类实例化、instanceof表达式、变量定义及引用、赋值表达式、自定义函数、对象属性存取及安全导航表达式、对象方法调用、Bean引用;

2.3 集合相关表达式

内联List、内联数组、集合,字典访问、列表,字典,数组修改、集合投影、集合选择;不支持多维内联数组初始化;不支持内联字典定义;

2.4 其他表达式

模板表达式。

三、主要类

3.1 ExpressionParser

表达式解析器接口,包含了(Expression) parseExpression(String), (Expression) parseExpression(String, ParserContext)两个接口方法。

java
1public interface ExpressionParser { 2 3 /** 4 * 解析表达式字符串并返回一个可用于重复评估的表达式对象。 5 */ 6 Expression parseExpression(String expressionString) throws ParseException; 7 8 /** 9 * 解析表达式字符串并返回一个可用于重复评估的表达式对象。 10 * context -- 用于影响此表达式解析例程的上下文(可选) 11 */ 12 Expression parseExpression(String expressionString, ParserContext context) throws ParseException; 13 14}

3.2 ParserContext

解析器上下文接口,主要是对解析器Token的抽象类,包含3个方法:getExpressionPrefix,getExpressionSuffix和isTemplate,就是表示表达式从什么符号开始什么符号结束,是否是作为模板(包含字面量和表达式)解析。

java
1public interface ParserContext { 2 3 /** 4 * 被解析的表达式是否是模板 5 */ 6 boolean isTemplate(); 7 8 /** 9 * 对于模板表达式,返回标识字符串中表达式块开始的前缀。例如:“${10 */ 11 String getExpressionPrefix(); 12 13 /** 14 * 对于模板表达式,返回标识字符串中表达式块结尾的前缀。例如: ”}15 */ 16 String getExpressionSuffix(); 17 18}

3.3 Expression

表达式的抽象,是经过解析后的字符串表达式的形式表示。通过expressionInstance.getValue方法,可以获取表示式的值。也可以通过调用getValue(EvaluationContext),从评估(evaluation)上下文中获取表达式对于当前上下文的值

java
1public interface Expression { 2 3 /** 4 * 返回用于创建此表达式的原始字符串(未修改) 5 */ 6 String getExpressionString(); 7 8 /** 9 * 在默认的标准上下文中计算这个表达式。 10 */ 11 @Nullable 12 Object getValue() throws EvaluationException; 13 14 /** 15 * 在默认上下文中计算表达式。如果评估的结果与预期的结果类型不匹配,则将返回异常。 16 */ 17 @Nullable 18 <T> T getValue(@Nullable Class<T> desiredResultType) throws EvaluationException; 19 20 /** 21 * 针对指定的根对象评估此表达式。 22 */ 23 @Nullable 24 Object getValue(Object rootObject) throws EvaluationException; 25 26 /** 27 * 针对指定的根对象评估此表达式。结果与预期的结果类型不匹配,则将返回异常。 28 */ 29 @Nullable 30 <T> T getValue(Object rootObject, @Nullable Class<T> desiredResultType) throws EvaluationException; 31 32 /** 33 * 在提供的上下文中评估此表达式并返回评估结果。 34 */ 35 @Nullable 36 Object getValue(EvaluationContext context) throws EvaluationException; 37 38 /** 39 * 在提供的上下文中评估此表达式并返回评估结果。但提供的根上下文会覆盖,默认的上下文 40 */ 41 @Nullable 42 Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException; 43 44 /** 45 * 在指定的上下文中评估表达式,该上下文可以解析对属性、方法、类型等的引用。评估结果的类型应为特定类,如果不是且无法转换为该类,将引发异常类型 46 */ 47 @Nullable 48 <T> T getValue(EvaluationContext context, @Nullable Class<T> desiredResultType) throws EvaluationException; 49 50 /** 51 * 在指定的上下文中评估表达式,该上下文可以解析对属性、方法、类型等的引用。评估结果的类型应为特定类,如果不是且无法转换为该类,将引发异常类型。提供的根对象覆盖在提供的上下文中指定的任何默认值。 52 */ 53 @Nullable 54 <T> T getValue(EvaluationContext context, Object rootObject, @Nullable Class<T> desiredResultType) 55 throws EvaluationException; 56 57 /** 58 * 返回可以使用默认上下文传递给setValue方法的最通用类型。 59 */ 60 @Nullable 61 Class<?> getValueType() throws EvaluationException; 62 63 /** 64 * 根据根对象,获取表达式的类型 65 */ 66 @Nullable 67 Class<?> getValueType(Object rootObject) throws EvaluationException; 68 69 /** 70 * 根据上下文,获取表达式的类型 71 */ 72 @Nullable 73 Class<?> getValueType(EvaluationContext context) throws EvaluationException; 74 75 /** 76 * 根据上下文,获取表达式的类型,root对象会覆盖上下文 77 */ 78 @Nullable 79 Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException; 80 81}

3.4 EvaluationContext

EvaluationContext在评估表达式以解析属性、方法或字段并帮助执行类型转换时,使用该接口。Spring 提供了两种实现。

  • SimpleEvaluationContext:针对不需要完整范围的 SpEL 语言语法且应受到有意义限制的表达式类别,公开了基本 SpEL 语言功能和配置选项的子集。示例包括但不限于数据绑定表达式和基于属性的过滤器。
  • StandardEvaluationContext:公开全套 SpEL 语言功能和配置选项。您可以使用它来指定默认根对象并配置每个可用的评估相关策略。

SimpleEvaluationContext旨在仅支持 SpEL 语言语法的一个子集。它不包括 Java 类型引用、构造函数和 bean 引用。它还要求您明确选择对表达式中的属性和方法的支持级别。默认情况下,create()静态工厂方法只允许对属性进行读取访问。您还可以获得构建器来配置所需的确切支持级别

如下示例。

java
1 // 字符串表达式 2 String exp = "Hello , #{ #username }"; 3 // 表达式解析器 4 ExpressionParser parser = new SpelExpressionParser(); 5 // 表达式上下文 6 EvaluationContext context = new StandardEvaluationContext(); 7 context.setVariable("username", "纹银三百两"); 8 // 解析 9 Expression expression = parser.parseExpression(exp, new TemplateParserContext()); 10 // Hello , 纹银三百两 11 System.out.println(expression.getValue(context, String.class));

3.5 SpelParserConfiguration

可以使用解析器配置对象 ( org.springframework.expression.spel.SpelParserConfiguration) 来配置 SpEL 表达式解析器。配置对象控制一些表达式组件的行为。例如,如果您对数组或集合进行索引,并且指定索引处的元素是null,SpEL可以自动创建元素。这在使用由属性引用链组成的表达式时很有用。

java
1class Demo { 2 public List<String> list; 3} 4 5// Turn on: 6// - 空引用,自动初始化 7// - 如果是集合,自动扩容 8SpelParserConfiguration config = new SpelParserConfiguration(true, true); 9 10ExpressionParser parser = new SpelExpressionParser(config); 11 12Expression expression = parser.parseExpression("list[3]"); 13 14Demo demo = new Demo(); 15 16Object o = expression.getValue(demo);

四、案例运用

4.1 文字表达

text
1@Test 2public void baseTest() { 3 // 字符串表达式 4 String exp = "Hello , #{ #username }"; 5 // 表达式解析器 6 ExpressionParser parser = new SpelExpressionParser(); 7 // 表达式上下文 8 EvaluationContext context = new StandardEvaluationContext(); 9 context.setVariable("username", "纹银三百两"); 10 // 解析 11 Expression expression = parser.parseExpression(exp, new TemplateParserContext()); 12 // Hello , 纹银三百两 13 System.out.println(expression.getValue(context, String.class)); 14 15 // Hello World! 16 Expression exp = parser.parseExpression("'Hello World'.concat('!')"); 17 String message = (String) exp.getValue(); 18 19 Expression exp2 = parser.parseExpression("'Hello World'.bytes"); 20 byte[] bytes = (byte[]) exp2.getValue(); 21 22 // invokes 'getBytes().length' 23 Expression exp = parser.parseExpression("'Hello World'.bytes.length"); 24 int length = (Integer) exp.getValue(); 25 26 27 Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); 28 String message = exp.getValue(String.class); 29 } 30

4.2 关系运算符

text
1//true 2boolean trueValue1 = parser.parseExpression("2 == 2").getValue(Boolean.class); 3//false 4boolean falseValue1 = parser.parseExpression("2 < -5.0").getValue(Boolean.class); 5//true 6boolean trueValue2 = parser.parseExpression("'black' < 'block'").getValue(Boolean.class); 7//false,字符xyz是否为int类型 8boolean falseValue2 = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class); 9//true,正则是否匹配 10boolean trueValue3 =parser.parseExpression("'5.00' matches '^-?d+(.d{2})?$'").getValue(Boolean.class); 11//false 12boolean falseValue3=parser.parseExpression("'5.0067' matches '^-?d+(.d{2})?$'").getValue(Boolean.class);

4.3 逻辑运算符

text
1// -- AND 与运算 -- 2//false 3boolean falseValue4 = parser.parseExpression("true and false").getValue(Boolean.class); 4 // -- OR 或运算-- 5//true 6boolean trueValue5 = parser.parseExpression("true or false").getValue(Boolean.class); 7//false 8boolean falseValue5 = parser.parseExpression("!true").getValue(Boolean.class);

4.4 算术运算符

text
1// Addition 2int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 3String testString = 4parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class); // 'test string' 5// Subtraction 6int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4 7double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000 8// Multiplication 9int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6 10double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0 11// Division 12int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2 13double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0 14// Modulus 15int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3 16int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1 17// Operator precedence 18int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21

4.5 三元运算符

您可以使用三元运算符在表达式内执行 if-then-else 条件逻辑。以下清单显示了一个最小示例:

java
1String falseString = parser.parseExpression( 2 "false ? 'trueExp' : 'falseExp'").getValue(String.class);

在这种情况下,布尔值false会返回字符串 value 'falseExp'。一个更现实的例子如下:

java
1 2public class SpringElTest { 3 4 public String name; 5 6 public boolean isMember(String name) { 7 return true; 8 } 9 @Test 10 public void test() throws Exception { 11 SpringElTest root = new SpringElTest(); 12 SpelExpressionParser parser = new SpelExpressionParser(); 13 StandardEvaluationContext context = new StandardEvaluationContext(root); 14 // 可以注册方法,注意如果是注册的方法要 #isMember(#queryName)而不是isMember(#queryName) 15// context.registerFunction("isMember", isMember); 16 context.setVariable("queryName", "周杰伦"); 17 18 // 绑定属性 19 EvaluationContext setContext = SimpleEvaluationContext.forReadWriteDataBinding().build(); 20 parser.parseExpression("name").setValue(setContext, root, "许嵩"); 21 22 String expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + 23 "+ name + ' Society' : #queryName + ' is not a member of the ' + name + ' Society'"; 24 String queryResultString = parser.parseExpression(expression) 25 .getValue(context, String.class); 26 // 周杰伦 is a member of the 许嵩 Society 27 System.out.println(queryResultString); 28 } 29} 30

4.6 使用变量

java
1Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); 2 3EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build(); 4context.setVariable("newName", "Mike Tesla"); 5 6parser.parseExpression("name = #newName").getValue(context, tesla); 7System.out.println(tesla.getName()) // "Mike Tesla"

五、集合操作

选择是一种强大的表达式语言功能,可让您通过从其条目中进行选择将源集合转换为另一个集合。

选择使用.?[selectionExpression]. 它过滤集合并返回一个包含原始元素子集的新集合。例如,选择可以让我们轻松获得塞尔维亚发明人的列表,如下例所示:

5.1 集合过滤

java
1// create an array of integers 2List<Integer> primes = new ArrayList<Integer>(); 3primes.addAll(Arrays.asList(2,3,5,7,11,13,17)); 4 5// create parser and set variable 'primes' as the array of integers 6ExpressionParser parser = new SpelExpressionParser(); 7EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess(); 8context.setVariable("primes", primes); 9 10// all prime numbers > 10 from the list (using selection ?{...}) 11// evaluates to [11, 13, 17] 12List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression( 13 "#primes.?[#this>10]").getValue(context);

5.2 集合映射

类似与map操作,语法是.![projectionExpression]

java
1// returns ['Smiljan', 'Idvor' ] 2List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");

六、操作类

6.1 类类型

text
1@Test 2public void classTypeTest() { 3 ExpressionParser parser = new SpelExpressionParser(); 4 //java.lang包类访问 5 Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class); 6 //class java.lang.String 7 System.out.println(result1); 8 9 //其他包类访问 10 String expression2 = "T(spel.SpElTest)"; 11 Class<SpElTest> value = parser.parseExpression(expression2).getValue(Class.class); 12 //true 13 System.out.println(value == SpElTest.class); 14 15 //类静态字段访问 16 int result3 = parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class); 17 //true 18 System.out.println(result3 == Integer.MAX_VALUE); 19 20 //类静态方法调用 21 int result4 = parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class); 22 //1 23 System.out.println(result4); 24 }

6.2 自定义函数

text
1/** 2 * 两数之和 3 */ 4public static Integer add(Integer x, Integer y) { 5 return x + y; 6 } 7 8@Test 9public void functionTest() throws NoSuchMethodException { 10 // 表达式 11 String exp = "#{ #add(4,5)}"; 12 // 表达式上下文 13 StandardEvaluationContext context = new StandardEvaluationContext(); 14 Method add = SpElTest.class.getDeclaredMethod("add", Integer.class, Integer.class); 15 context.registerFunction("add", add); 16 // 表达式解析器 17 ExpressionParser parser = new SpelExpressionParser(); 18 // 解析 19 Expression expression = parser.parseExpression(exp, new TemplateParserContext()); 20 // 9 21 System.out.println(expression.getValue(context, Integer.class)); 22 }

6.3 类属性

text
1 @Test 2 public void assignTest() { 3 String exp = "username: #{#user.username},age: #{#user.age}"; 4 StandardEvaluationContext context = new StandardEvaluationContext(); 5 Person person = new Person() 6 .setUsername("纹银三百两") 7 .setAge(23); 8 context.setVariable("user", person); 9 ExpressionParser parser = new SpelExpressionParser(); 10 Expression expression = parser.parseExpression(exp, new TemplateParserContext()); 11 //username: 纹银三百两,age: 23 12 System.out.println(expression.getValue(context, String.class)); 13 }

七、模板表达式

指定模板 %{ },默认是 #{}

text
1@Test 2public void templateTest() { 3 SpelExpressionParser parser = new SpelExpressionParser(); 4 ParserContext context = new TemplateParserContext("%{", "}"); 5 Expression expression = parser.parseExpression("你好:%{#name},正在学习:%{#lesson},加油、奋斗!!!", context); 6 EvaluationContext evaluationContext = new StandardEvaluationContext(); 7 evaluationContext.setVariable("name", "纹银三百两"); 8 evaluationContext.setVariable("lesson", "spring高手系列。"); 9 String value = expression.getValue(evaluationContext, String.class); 10 //你好:纹银三百两,正在学习:spring高手系列。加油、奋斗!!! 11 System.out.println(value); 12 }

八、规则引擎

8.1 背景

假设人员注册信息(姓名、年龄、性别),自定义其中规则,如下:

李家好汉(李姓,男,且满18岁) 豆蔻少女(13-15岁,女性)

8.2 实现

text
1@Test 2 public void ruleTest() { 3 Person person1 = new Person().setUsername("小龙女").setAge(14).setSex(1); 4 checkRule(FastJsonUtil.parseMap(JSON.toJSONString(person1))); 5 Person person2 = new Person().setUsername("张三").setAge(18).setSex(0); 6 checkRule(FastJsonUtil.parseMap(JSON.toJSONString(person2))); 7 Person person3 = new Person().setUsername("李四").setAge(20).setSex(0); 8 checkRule(FastJsonUtil.parseMap(JSON.toJSONString(person3))); 9 10 } 11 12 /** 13 * 规则check 14 * 15 * @param exp 参数map 16 */ 17 private static void checkRule(Map<String, Object> exp) { 18 ExpressionParser parser = new SpelExpressionParser(); 19 //规则容器 20 Map<String, String> ruleMap = Maps.newHashMap(); 21 String rule1 = "( #username.contains({'李'}) and #age > 18 and #sex == 0 )"; 22 ruleMap.put("李家好汉", rule1); 23 String rule2 = "( #age between {13,15} and #sex == 1 )"; 24 ruleMap.put("豆蔻少女", rule2); 25 EvaluationContext spElContext = getSpElContext(exp); 26 ruleMap.keySet().forEach(key -> { 27 String ruleV = ruleMap.get(key); 28 Boolean isPass = parser.parseExpression(ruleV).getValue(spElContext, Boolean.class); 29 if (Objects.nonNull(isPass) && isPass) { 30 System.out.println("username:【" + exp.get("username") + "】,命中规则:【" + key+"】"); 31 } 32 33 }); 34 } 35 36 /** 37 * 解析表达式需要的上下文,透传请求参数 38 * 39 * @param param 参数 40 * @return 返回结果 41 */ 42 private static EvaluationContext getSpElContext(Map<String, Object> param) { 43 StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); 44 for (Entry<String, Object> entry : param.entrySet()) { 45 if (entry.getValue() != null) { 46 evaluationContext.setVariable(entry.getKey(), entry.getValue()); 47 } 48 } 49 return evaluationContext; 50 }

结果:

text
1username:【小龙女】,命中规则:【豆蔻少女】 2username:【李四】,命中规则:【李家好汉】

九、容器内使用

9.1 注释配置

java
1public class FieldValueTestBean { 2 3 @Value("#{ systemProperties['user.region'] }") 4 private String defaultLocale; 5 6 public void setDefaultLocale(String defaultLocale) { 7 this.defaultLocale = defaultLocale; 8 } 9 10 public String getDefaultLocale() { 11 return this.defaultLocale; 12 } 13} 14 15public class PropertyValueTestBean { 16 17 private String defaultLocale; 18 19 @Value("#{ systemProperties['user.region'] }") 20 public void setDefaultLocale(String defaultLocale) { 21 this.defaultLocale = defaultLocale; 22 } 23 24 public String getDefaultLocale() { 25 return this.defaultLocale; 26 } 27}

9.2 自动装配

java
1public class SimpleMovieLister { 2 3 private MovieFinder movieFinder; 4 private String defaultLocale; 5 6 @Autowired 7 public void configure(MovieFinder movieFinder, 8 @Value("#{ systemProperties['user.region'] }") String defaultLocale) { 9 this.movieFinder = movieFinder; 10 this.defaultLocale = defaultLocale; 11 } 12 13 // ... 14} 15 16public class MovieRecommender { 17 18 private String defaultLocale; 19 20 private CustomerPreferenceDao customerPreferenceDao; 21 22 public MovieRecommender(CustomerPreferenceDao customerPreferenceDao, 23 @Value("#{systemProperties['user.country']}") String defaultLocale) { 24 this.customerPreferenceDao = customerPreferenceDao; 25 this.defaultLocale = defaultLocale; 26 } 27 28 // ... 29}

十、总结

Spring EL表达式,作为JAVA的内置语言,十分强大。主要可以用来做表达式解析,或者规则链路,且可以操作函数方法;从而达到一种动态的链路规则解析效果。

最后,都看到这里了,最后如果这篇文章,对你有所帮助,请点个关注,交个朋友。