从零搭建开发脚手架 Spring EL表达式的简介和实战应用

开发 后端
Sping EL(Spring Expression Language 简称 SpEL)是一种强大的表达式语言,支持在运行时查询和操作对象,它可以与 XML 或基于注解的 Spring 配置一起使用。语言语法类似于统一 EL,但提供了额外的功能,方法调用和字符串模板功能。
本文转载自微信公众号「Java大厂面试官」,作者laker。转载本文请联系Java大厂面试官公众号。
  • 简介
    • 算术运算符
    • 关系运算符
    • 逻辑运算符
    • 三目运算符
    • 正则运算符
    • 访问List和Map
  • 以编程方式解析表达式
    • ExpressionParser
    • EvaluationContext
  • 高级应用
    • Bean引用
    • #this和#root
    • 表达式模板
  • 实战
    • 1.注册常用的用户、Request、Response、工具类到上下文
    • 2.访问Spring容器中的任意Bean并调用其方法
    • 3.自定义注解+获取方法入参

简介

Sping EL(Spring Expression Language 简称 SpEL)是一种强大的表达式语言,支持在运行时查询和操作对象,它可以与 XML 或基于注解的 Spring 配置一起使用。语言语法类似于统一 EL,但提供了额外的功能,方法调用和字符串模板功能。

虽然还有其他几种可用的 Java 表达式语言,OGNL、MVEL 和 JBoss EL等,但创建 Spring 表达式语言是为了向 Spring 社区提供一种受良好支持的表达式语言,SpEL基于与技术无关的 API,允许在需要时集成其他表达式语言实现。

SpEL支持以下运算符

类型 操作符
算术运算符 +, -, *, /, %, ^, div, mod
关系运算符 <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge
逻辑运算符 and, or, not, &&, ||, !
三目运算符 ?:
正则运算符 matches

以注解的方式举例如下:

SpEL表达式以#符号开头,并用大括号括起来:#{expression}。可以以类似的方式引用属性,以$符号开头,并用大括号括起来:${property.name}。属性占位符不能包含 SpEL 表达式,但表达式可以包含属性引用.

  1. #{${someProperty} + 2} 
  2. someProperty 的值为 2,计算结果为 4。 

算术运算符

支持所有基本算术运算符。

  1. @Value("#{19 + 1}") // 20 
  2. private double add;  
  3.  
  4. @Value("#{'String1 ' + 'string2'}") // "String1 string2" 
  5. private String addString;  
  6.  
  7. @Value("#{20 - 1}") // 19 
  8. private double subtract; 
  9.  
  10. @Value("#{10 * 2}") // 20 
  11. private double multiply; 
  12.  
  13. @Value("#{36 / 2}") // 19 
  14. private double divide; 
  15.  
  16. @Value("#{36 div 2}") // 18, the same as for / operator 
  17. private double divideAlphabetic;  
  18.  
  19. @Value("#{37 % 10}") // 7 
  20. private double modulo; 
  21.  
  22. @Value("#{37 mod 10}") // 7, the same as for % operator 
  23. private double moduloAlphabetic;  
  24.  
  25. @Value("#{2 ^ 9}") // 512 
  26. private double powerOf; 
  27.  
  28. @Value("#{(2 + 2) * 2 + 9}") // 17 
  29. private double brackets; 

关系运算符

  1. @Value("#{1 == 1}") // true 
  2. private boolean equal; 
  3.  
  4. @Value("#{1 eq 1}") // true 
  5. private boolean equalAlphabetic; 
  6.  
  7. @Value("#{1 != 1}") // false 
  8. private boolean notEqual; 
  9.  
  10. @Value("#{1 ne 1}") // false 
  11. private boolean notEqualAlphabetic; 
  12.  
  13. @Value("#{1 < 1}") // false 
  14. private boolean lessThan; 
  15.  
  16. @Value("#{1 lt 1}") // false 
  17. private boolean lessThanAlphabetic; 
  18.  
  19. @Value("#{1 <= 1}") // true 
  20. private boolean lessThanOrEqual; 
  21.  
  22. @Value("#{1 le 1}") // true 
  23. private boolean lessThanOrEqualAlphabetic; 
  24.  
  25. @Value("#{1 > 1}") // false 
  26. private boolean greaterThan; 
  27.  
  28. @Value("#{1 gt 1}") // false 
  29. private boolean greaterThanAlphabetic; 
  30.  
  31. @Value("#{1 >= 1}") // true 
  32. private boolean greaterThanOrEqual; 
  33.  
  34. @Value("#{1 ge 1}") // true 
  35. private boolean greaterThanOrEqualAlphabetic; 

逻辑运算符

  1. @Value("#{250 > 200 && 200 < 4000}") // true 
  2. private boolean and;  
  3.  
  4. @Value("#{250 > 200 and 200 < 4000}") // true 
  5. private boolean andAlphabetic; 
  6.  
  7. @Value("#{400 > 300 || 150 < 100}") // true 
  8. private boolean or
  9.  
  10. @Value("#{400 > 300 or 150 < 100}") // true 
  11. private boolean orAlphabetic; 
  12.  
  13. @Value("#{!true}") // false 
  14. private boolean not
  15.  
  16. @Value("#{not true}") // false 
  17. private boolean notAlphabetic; 

三目运算符

  1. @Value("#{2 > 1 ? 'a' : 'b'}") // "a" 
  2. private String ternary; 
  3. @Value("#{someBean.someProperty != null ? someBean.someProperty : 'default'}"
  4. private String ternary; 

正则运算符

  1. @Value("#{'100' matches '\\d+' }") // true 
  2. private boolean validNumericStringResult; 
  3.  
  4. @Value("#{'100fghdjf' matches '\\d+' }") // false 
  5. private boolean invalidNumericStringResult; 
  6.  
  7. @Value("#{'valid alphabetic string' matches '[a-zA-Z\\s]+' }") // true 
  8. private boolean validAlphabeticStringResult; 
  9.  
  10. @Value("#{'invalid alphabetic string #$1' matches '[a-zA-Z\\s]+' }") // false 
  11. private boolean invalidAlphabeticStringResult; 
  12.  
  13. @Value("#{someBean.someValue matches '\d+'}") // true if someValue contains only digits 
  14. private boolean validNumericValue; 

访问List和Map

  1. @Component("workersHolder"
  2. public class WorkersHolder { 
  3.     private List<String> workers = new LinkedList<>(); 
  4.     private Map<String, Integer> salaryByWorkers = new HashMap<>(); 
  5.  
  6.     public WorkersHolder() { 
  7.         workers.add("John"); 
  8.         workers.add("Susie"); 
  9.         workers.add("Alex"); 
  10.         workers.add("George"); 
  11.  
  12.         salaryByWorkers.put("John", 35000); 
  13.         salaryByWorkers.put("Susie", 47000); 
  14.         salaryByWorkers.put("Alex", 12000); 
  15.         salaryByWorkers.put("George", 14000); 
  16.     } 
  17.  
  18.     //Getters and setters 
  19.  
  20.  
  21. @Value("#{workersHolder.salaryByWorkers['John']}") // 35000 
  22. private Integer johnSalary; 
  23.  
  24. @Value("#{workersHolder.salaryByWorkers['George']}") // 14000 
  25. private Integer georgeSalary; 
  26.  
  27. @Value("#{workersHolder.salaryByWorkers['Susie']}") // 47000 
  28. private Integer susieSalary; 
  29.  
  30. @Value("#{workersHolder.workers[0]}") // John 
  31. private String firstWorker; 
  32.  
  33. @Value("#{workersHolder.workers[3]}") // George 
  34. private String lastWorker; 
  35.  
  36. @Value("#{workersHolder.workers.size()}") // 4 
  37. private Integer numberOfWorkers; 

以编程方式解析表达式

ExpressionParser

  • ExpressionParser负责解析表达式字符串,可以使用它来调用方法、访问属性或调用构造函数。
  1. Expression expression = expressionParser.parseExpression("'Any string'.length()"); 
  2. Integer result = (Integer) expression.getValue(); 
  3.  
  4. Expression expression = expressionParser.parseExpression("new String('Any string').length()"); 
  5.  
  6. Expression expression = expressionParser.parseExpression("'Any string'.replace(\" \", \"\").length()"); 
  7. Integer result = expression.getValue(Integer.class); 
  8.  
  9. Car car = new Car(); 
  10. car.setMake("Good manufacturer"); 
  11. car.setModel("Model 3"); 
  12. car.setYearOfProduction(2014); 
  13. ExpressionParser expressionParser = new SpelExpressionParser(); 
  14. Expression expression = expressionParser.parseExpression("model"); 
  15.  
  16. EvaluationContext context = new StandardEvaluationContext(car); 
  17. String result = (String) expression.getValue(context); 
  18.  
  19. Expression expression = expressionParser.parseExpression("yearOfProduction > 2005"); 
  20. boolean result = expression.getValue(car, Boolean.class); 
  21.  
  22. Expression expression = expressionParser.parseExpression("model"); 
  23. String result = (String) expression.getValue(car); 
  • 使用ExpressionParser设置值
  1. StandardEvaluationContext context = new StandardEvaluationContext(carPark); 
  2.  
  3. ExpressionParser expressionParser = new SpelExpressionParser(); 
  4. expressionParser.parseExpression("cars[0].model").setValue(context, "Other model"); 
  • ExpressionParser解析器配置
  1. SpelParserConfiguration config = new SpelParserConfiguration(truetrue); 
  2. StandardEvaluationContext context = new StandardEvaluationContext(carPark); 
  3.  
  4. ExpressionParser expressionParser = new SpelExpressionParser(config); 
  5. expressionParser.parseExpression("cars[0]").setValue(context, car); 
  6.  
  7. Car result = carPark.getCars().get(0); 
  • 允许它在指定索引为空时自动创建元素(*autoGrowNullReferences,*构造函数的第一个参数)
  • 自动增长数组或列表以容纳超出其初始大小的元素(autoGrowCollections,第二个参数)

EvaluationContext

当计算表达式解析properties, methods, fields,并帮助执行类型转换, 使用接口EvaluationContext 这是一个开箱即用的实现, StandardEvaluationContext,使用反射来操纵对象, 缓存java.lang.reflect的Method,Field,和Constructor实例 提高性能。

  1. class Simple { 
  2.     public List<Boolean> booleanList = new ArrayList<Boolean>(); 
  3.  
  4. Simple simple = new Simple(); 
  5.  
  6. simple.booleanList.add(true); 
  7.  
  8. StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple); 
  9.  
  10. // false is passed in here as a string. SpEL and the conversion service will 
  11. // correctly recognize that it needs to be a Boolean and convert it 
  12. parser.parseExpression("booleanList[0]").setValue(simpleContext, "false"); 
  13.  
  14. // b will be false 
  15. Boolean b = simple.booleanList.get(0); 

高级应用

Bean引用

如果解析上下文已经配置,那么bean解析器能够 从表达式使用(@)符号查找bean类。

  1. ExpressionParser parser = new SpelExpressionParser(); 
  2. StandardEvaluationContext context = new StandardEvaluationContext(); 
  3. context.setBeanResolver(new MyBeanResolver()); 
  4.  
  5. // This will end up calling resolve(context,"foo"on MyBeanResolver during evaluation 
  6. Object bean = parser.parseExpression("@foo").getValue(context); 

如果需要获取Bean工厂本身而不是它构造的Bean,可以使用&Bean名称。

  1. Object bean = parser.parseExpression("&foo").getValue(context); 

#this和#root

#this和#root代表了表达式上下文的对象,#root就是当前的表达式上下文对象,#this则根据当前求值环境的不同而变化。下面的例子中,#this即每次循环的值。

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

表达式模板

表达式模板使用#{}定义,它允许我们混合多种结果。下面就是一个例子,首先Spring会先对模板中的表达式求值,在这里是返回一个随机值,然后将结果和外部的表达式组合起来。最终的结果就向下面这样了。

  1. String randomPhrase = parser.parseExpression( 
  2.         "random number is #{T(java.lang.Math).random()}"
  3.         new TemplateParserContext()).getValue(String.class); 
  4. // 结果是 "random number is 0.7038186818312008" 

实战

以上都是官网的理论值,现总结下项目实战中常用的技巧。

1.注册常用的用户、Request、Response、工具类到上下文

注册常用的用户、Request、Response、工具类到上下文,以便于在表达式中引用业务无关的对象。

  1. ExpressionParser parser = new SpelExpressionParser();// 这个是线程安全的 定义为全局变量。   
  2. String expression = "#{user.id + request.getQuringString()}"
  3. Expression exp = parser.parseExpression(expression); 
  4.  
  5. EvaluationContext context = new StandardEvaluationContext(); 
  6. context.setVariable("user"user); 
  7. context.setVariable("request", request); 
  8. context.setVariable("dateUtils", dateUtils); 
  9.          
  10. String value = (String) exp.getValue(context); 

2.访问Spring容器中的任意Bean并调用其方法

要访问 bean 对象,那么EvaluationContext中需要包含 bean 对象才行,可以借助BeanResolver来实现,如context.setBeanResolver(new BeanFactoryResolver(applicationContext)),访问 bean 的前缀修饰为@符号。

我们需要获取ApplicationContext,可以继承ApplicationContextAware,或者使用@Autowired获取。

  1. StandardEvaluationContext context = new StandardEvaluationContext(); 
  2. context.setBeanResolver(new BeanFactoryResolver(applicationContext));  
  3.  
  4. // 获取bean对象 
  5. LakerService lakerService = parser.parseExpression("@lakerService").getValue(context, LakerService.class); 
  6. System.out.println("lakerService : " + lakerService); 
  7.  
  8. // 访问bean方法 
  9. String result = parser.parseExpression("@lakerService.print('lakernote')").getValue(context, String.class); 
  10. System.out.println("return result : " + result); 

3.自定义注解+获取方法入参

1.定义自定义注解

  1. @Target(ElementType.METHOD) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. @Documented 
  4. public @interface Laker { 
  5.     String value(); 

2.针对自定义注解定义切面拦截

  1. @Aspect 
  2. @Component 
  3. @Slf4j 
  4. public class LakerAspect { 
  5.     private SpelExpressionParser parserSpel = new SpelExpressionParser(); 
  6.     private DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); 
  7.  
  8.     @Pointcut("@annotation(com.laker.map.moudle.spel.Laker)"
  9.     private void elPoint() { 
  10.  
  11.     } 
  12.  
  13.     @Around("elPoint()"
  14.     public void cache(ProceedingJoinPoint pjp) { 
  15.         Method method = ((MethodSignature) pjp.getSignature()).getMethod(); 
  16.         Laker laker = method.getAnnotation(Laker.class); 
  17.         String value = getValue(laker.value(), pjp); 
  18.         log.info(value); 
  19.         try { 
  20.             pjp.proceed(); 
  21.         } catch (Throwable e) { 
  22.             log.error("", e); 
  23.         } 
  24.     } 
  25.  
  26.     public String getValue(String key, ProceedingJoinPoint pjp) { 
  27.         Expression expression = parserSpel.parseExpression(key); 
  28.         EvaluationContext context = new StandardEvaluationContext(); 
  29.          
  30.         User user = new User(); 
  31.         user.id = 123L; 
  32.         context.setVariable("user"user);// 模拟设置用户信息 
  33.          
  34.         MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); 
  35.         Object[] args = pjp.getArgs(); 
  36.         String[] paramNames = parameterNameDiscoverer.getParameterNames(methodSignature.getMethod()); 
  37.         for (int i = 0; i < args.length; i++) { 
  38.             context.setVariable(paramNames[i], args[i]); 
  39.         } 
  40.         return expression.getValue(context).toString(); 
  41.     } 
  42.  
  43.     class User { 
  44.         public Long id; 
  45.     } 

3.在业务类上使用自定义注解

  1. @Service 
  2. public class LakerService { 
  3.  
  4.     @Laker("#user.id + #msg") //要符合SpEL表达式格式 
  5.     public void print(String msg) { 
  6.         System.out.println(msg); 
  7.     } 

参考:

 

  • https://docs.spring.io/spring-framework/docs/current/reference/html/
  • https://www.baeldung.com/spring-expression-language

 

责任编辑:武晓燕 来源: Java大厂面试官
相关推荐

2021-07-13 18:42:38

Spring Boot脚手架开发

2021-04-28 16:10:48

开发脚手架 Spring

2020-08-19 08:55:47

Redis缓存数据库

2021-09-01 10:07:43

开发零搭建Groovy

2021-06-02 17:58:49

脚手架 幂等性前端

2021-02-19 22:43:50

开发脚手架Controller

2021-05-13 17:02:38

MDC脚手架日志

2021-04-13 14:47:53

认证授权Java

2021-03-09 17:11:09

数据库脚手架开发

2021-03-11 14:16:47

Spring Boo开发脚手架

2021-04-20 19:24:16

脚手架 Java微信

2016-08-10 14:59:41

前端Javascript工具

2016-09-07 15:35:06

VueReact脚手架

2023-11-21 17:36:04

OpenFeignSentinel

2021-01-07 05:34:07

脚手架JDK缓存

2018-06-11 14:39:57

前端脚手架工具node.js

2018-08-30 16:08:37

Node.js脚手架工具

2020-06-29 11:35:02

Spring BootJava脚手架

2014-08-15 09:36:06

2022-07-18 07:58:46

Spring工具工具类
点赞
收藏

51CTO技术栈公众号