基于Spring EL表达式处理业务表达式

server/2024/9/24 8:28:02/

文章目录

简介

SpringEL是Spring表达式,功能非常强大。

我们可以能在@Value、@Cachable、自定义注解中的应用EL表达式,当然这些不是本文的重点。

本文的重点是如何基于SpringEL来定制业务表达式,以便于简化我们工作,减少自己去处理解析表达式的复杂逻辑。

一个简单的场景:

例如,业务希望文件名是动态的,根据实际的年、月、日、季以及其他的业务数据来生成。

因为有非常多的文件类型,业务希望通过表达式根据实际业务数据生成最终文件名。

如果仅仅是简单的年月日,自己写个简单的表达式解析器问题也不大,但是还有根据实际业务数据会有很多表达式,这种倒不是说不能写,但是复杂度就会比较高,测试起来就非常复杂,很难覆盖到所有的场景情况。

这时就可以基于SpringEL来定制开发。

Spring EL的常见应用

@Value

java">//注入操作系统属性
@Valule("#{systemProperties['os.name']}")
private String os;
//注入表达式结果
@Valule("#{T(java.lang.Math).random()*100}")
private Double randomNumber;
//注入其他bean属性
@Valule("#{myBean.name}")
private String name;// 属性表达式,在配置文件找app.count属性注入
@Value("${app.count}")
private Integer count;// EL表达式中包含$属性表达式
@Value("#{T(Integer).parseInt('${config.num:10}')}")
private Integer num;

这里要注意:#和$

Spring中:

  1. ${}是属性表达式,引用的是properties和yaml配置文件中的属性值
  2. #{}是EL表达式默认的模板

${}属性表达式在应用启动时就会被解析,在配置加载的时候进行替换,
#{}EL表达式是运行时解析

${}属性表达式比#{}EL表达式先执行,所以可以在EL表达式中包含${}属性表达式

处理注解

最常见的就是缓存、分布式锁等注解,可以根据方法的注解、结合实际调用的参数,计算缓存的key和分布式锁的key。

通过支持SpringEL,在设置的时候可以更加灵活。

java">@Aspect
@Component
public class KeyAspect {@Around("@annotation(disLock)")public Object around(ProceedingJoinPoint joinPoint, DisLcok disLock) throws Throwable {EvaluationContext context = new StandardEvaluationContext();MethodSignature signature = (MethodSignature) joinPoint.getSignature();Object[] args = joinPoint.getArgs();String[] parametersNames = new DefaultParameterNameDiscoverer().getParameterNames(signature.getMethod());for (int i = 0; i < args.length; i++) {context.setVariable(parametersNames[i], args[i]);}// 计算keyString lockKey = new SpelExpressionParser().parseExpression(disLock.key()).getValue(context, String.class);// 处理缓存、锁等逻辑return joinPoint.proceed();}
}

xml中使用

<bean id="myBean" class="vip.meet.MyBean"><property name="name" value="allen" /><property name="id" value="10" />
</bean><bean id="customerBean" class="vip.meet.CustomerBean"><property name="item" value="#{myBean}" /><property name="itemName" value="#{myBean.name}" />
</bean>

Spring EL表达式

这里就不详解EL的各种用法了,只是介绍一下注意事项和Spring EL能完成那些操作。

基本表达式

java">@Test
public void basic() {ExpressionParser parser = new SpelExpressionParser();// String操作System.out.println(parser.parseExpression("'Hello World'.concat('!')").getValue(String.class));// 运算符System.out.println(parser.parseExpression("10+2-3*4/2").getValue(Integer.class));// 访问静态变量 T表示类型,如果没有指定包,默认是java.lang下的类System.out.println(parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class));// 和上一个等价System.out.println(parser.parseExpression("T(java.lang.Integer).MAX_VALUE").getValue(int.class));// 访问静态方法System.out.println(parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class));// 逻辑表达式System.out.println(parser.parseExpression("2>1 and (!true or !false)").getValue(boolean.class));
}

模板

java">@Test
public void templateWorld(){ExpressionParser parser = new SpelExpressionParser();EvaluationContext context = new StandardEvaluationContext();context.setVariable("bj", "北京");context.setVariable("cd", "成都");context.setVariable("sh", "上海");String template = "Hello #{#bj},你好 #{#cd},Hi #{#sh}";Expression expression = parser.parseExpression(template, new TemplateParserContext());System.out.println(expression.getValue(context, String.class));
}

其中#{}是TemplateParserContext模板指定的表达式表达式中的#表示引用变量。

没有指定ParserContext,就不能解析多个,只能解析单个变量,如:

java">@Test
public void templateDefault(){ExpressionParser parser = new SpelExpressionParser();EvaluationContext context = new StandardEvaluationContext();context.setVariable("bj", "北京");Expression expression = parser.parseExpression("#bj");System.out.println(expression.getValue(context, String.class));
}

函数表达式

EL中还有一个非常有用的就是可以引用函数。

java">public class SpringELFunctionTest {public static Integer add(Integer x, Integer y) {return x + y;}@Testpublic void functionTest() throws NoSuchMethodException {String exp = "#{ #add(4,5)}";StandardEvaluationContext context = new StandardEvaluationContext();Method add = SpringELFunctionTest.class.getDeclaredMethod("add", Integer.class, Integer.class);context.registerFunction("add", add);ExpressionParser parser = new SpelExpressionParser();Expression expression = parser.parseExpression(exp, new TemplateParserContext());System.out.println(expression.getValue(context, Integer.class));}
}

还可以带参数:

java">@Data
private static class Param{private Date birthday;private Integer id;private String name;
}private static class Fun{public String getParam(Param param){return "hello " + param.toString();}
}@Test
public void assignTest() {String exp = "#{#fun.getParam(#param)} 啊哈娘子";StandardEvaluationContext evaluationContext = new StandardEvaluationContext();Fun fun = new Fun();evaluationContext.setVariable("fun", fun);Param param = new Param();param.setId(1);param.setBirthday(new Date());param.setName("tim");evaluationContext.setVariable("param", param);ExpressionParser parser = new SpelExpressionParser();TemplateParserContext parserContext = new TemplateParserContext();Expression expression = parser.parseExpression(exp, parserContext);System.out.println(expression.getValue(evaluationContext, String.class));
}

Spring EL定制

Spring因为${}是属性表达式,所以,EL的表达式默认是#{}

如果,给业务用的表达式我们希望是${},该怎么做呢?

例如:

表达式

${name}-${year}年${month}月${day}日-${quarter}季报.xlsx

计算之后得到类似:阿宝基金-2024年$12月31日-4季报.xlsx

一看,简单指定TemplateParserContext就可以,那么下面的方式可以行吗?

java">@Test
public void templateDefault(){ExpressionParser parser = new SpelExpressionParser();EvaluationContext context = new StandardEvaluationContext();context.setVariable("name", "阿宝基金");context.setVariable("year", "2024");context.setVariable("month", "12");context.setVariable("day", "31");context.setVariable("quarter", "4");String template = "${name}-${year}年${month}月${day}日-${quarter}季报.xlsx";TemplateParserContext parserContext = new TemplateParserContext("${","}");Expression expression = parser.parseExpression(template, parserContext);System.out.println(expression.getValue(context, String.class));
}

答案是,不行,表达式应该如下:

java">String template = "${#name}-${#year}年${#month}月${#day}日-${#quarter}季报.xlsx";

上面的表达式,看起来不够简化,我就希望使用下面这个表达式,怎么办?

java">String template = "${name}-${year}年${month}月${day}日-${quarter}季报.xlsx";

可以利用StandardEvaluationContext的root对象。

java">import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Map;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CustomParam {private Integer year;private Integer month;private Integer day;private Integer quarter;private String name;private Map<String,Object> paramMap;public static Integer staticMethod(){System.out.println("执行staticMethod");return 12;}public String instanceMethodParam(String... params){StringBuilder sb = new StringBuilder();for(String p : params){sb.append(p).append("@");}return sb.toString();}
}
java">@Test
public void templateRoot(){ExpressionParser parser = new SpelExpressionParser();HashMap<String, Object> map = new HashMap<>();map.put("p1","pa");map.put("p2","pb");CustomParam param = CustomParam.builder().name("阿宝基金").year(2024).month(12).day(31).quarter(4).paramMap(map).build();EvaluationContext context = new StandardEvaluationContext(param);String template = "${name}-${year}年${month}月${day}日-${quarter}季报.xlsx";TemplateParserContext parserContext = new TemplateParserContext("${","}");Expression expression = parser.parseExpression(template, parserContext);// 阿宝基金-2024年12月31日-4季报.xlsxSystem.out.println(expression.getValue(context, String.class));template = "function-${staticMethod()}-${instanceMethodParam('Hello','Hi','World')}.xlsx";expression = parser.parseExpression(template, parserContext);// function-12-Hello@Hi@World@.xlsxSystem.out.println(expression.getValue(context, String.class));template = "map-${paramMap['p1']}-${paramMap['p2']}.xlsx";expression = parser.parseExpression(template, parserContext);// map-pa-pb.xlsxSystem.out.println(expression.getValue(context, String.class));
}

重点在:EvaluationContext context = new StandardEvaluationContext(param);

StandardEvaluationContext的表达式,没有#默认是找root的对象的对应属性,

例如:

${name}就等价于${#root.name}

前面没有设置StandardEvaluationContext的root,root为空,所以:

${name}-${year}年${month}月${day}日-${quarter}季报.xlsx表达式自然有问题。

设置了root对象,能找到对应属性,自然就没问题了。

我们可以看到,不仅仅可以访问对应的属性,可以访问对应的实例方法和静态方法。

设置map,可以提供参数的灵活性。

引用Spring的bean

Spring EL还可以引用Spring容器中的Bean,通过@,Spring中所有的类和方法都可以引用,非常灵活。

java">import lombok.Getter;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;@Component
@Getter
public class ApplicationContextHolder implements ApplicationContextAware {private ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}public <T> T getBean(Class<T> clazz){return applicationContext.getBean(clazz);}
}
java">import org.springframework.stereotype.Service;@Service(value="elService")
public class ELService {public String service(){return "el service";}
}
java">import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import vip.meet.common.spring.ApplicationContextHolder;@SpringBootTest
public class SpringELBeanTest {@Resourceprivate ApplicationContextHolder applicationContextHolder;@Testpublic void bean() {ExpressionParser parser = new SpelExpressionParser();StandardEvaluationContext context = new StandardEvaluationContext();BeanFactoryResolver beanResolver = new BeanFactoryResolver(applicationContextHolder.getApplicationContext());context.setBeanResolver(beanResolver);String result = parser.parseExpression("@elService.service()").getValue(context, String.class);System.out.println(result);}
}

http://www.ppmy.cn/server/35525.html

相关文章

前端面试题(二)

面试形式&#xff1a;线上面试&#xff08;不露脸&#xff09;&#xff1a;时长40分钟 面试评价&#xff1a;由易到难&#xff0c;由细到全&#xff0c;比较不错 面试官&#xff1a;项目经理 面试官提问&#xff08;面试题&#xff09;&#xff1a; 1、聊聊最近写的这个项目…

Rust Rocket创建第一个hello world的Web程序 Rust Rocket开发常用网址和Rust常用命令

一、Rust Rocket简介 Rust Rocket 是一个用 Rust 语言编写的 Web 应用框架&#xff0c;它结合了 Rust 的安全性和性能优势&#xff0c;以及 Web 开发的便利性。以下是 Rust Rocket 框架的一些优点&#xff1a; 安全性&#xff1a;Rust 是一种注重安全性的编程语言&#xff0c;…

C语言创建文件夹和多级目录

C调用系统命令创建多级目录 #include <stdio.h> #include <stdlib.h>int main() {const char *path "a/b/c";// 创建目录命令的字符串char mkdir_command[100];sprintf(mkdir_command, "mkdir %s", path);// 调用系统命令system(mkdir_comma…

数据库原理与应用实验三 嵌套查询

实验目的和要求 加深和掌握对嵌套查询的理解和应用 实验环境 Windows10 SQLServer 实验内容与过程 图书&#xff08;书号&#xff0c;书名&#xff0c;价格&#xff0c;出版社&#xff09; 读者&#xff08;卡号&#xff0c;姓名&#xff0c;年龄&#xff0c;所属单位&a…

扭矩法是什么拧紧工艺?——SunTorque智能扭矩系统

智能扭矩系统-智能拧紧系统-扭矩自动控制系统-SunTorque 扭矩法是一种在拧紧工艺中广泛使用的预紧力控制方法。它基于扭矩与预紧力的线性关系&#xff0c;通过控制扭矩值来实现对被连接件的预紧。这种方法的优点在于操作简单&#xff0c;只需对扭矩进行控制即可。 在扭矩法拧…

浅记SpringBoot3源码中应用的JDK新特性

问题背景 在看SpringBoot3涉及到SpringMVC源码的时候&#xff0c;看到这么个东西。 // Bean name or resolved handler?if (handler instanceof String handlerName) {handler obtainApplicationContext().getBean(handlerName);}在想这是什么操作&#xff1f; 后来查到这是…

【Kafka每日一问】Kafka分区分配策略有哪些?

Kafka分区分配策略有哪些 Range分配策略&#xff08;Range&#xff09;&#xff1a; 这是默认的分配策略。在这种策略下&#xff0c;每个消费者负责订阅主题的连续范围内的分区。例如&#xff0c;如果有两个消费者和一个主题有12个分区&#xff0c;那么第一个消费者将负责分区0…

秋招后端开发面试题 - MySQL事务

目录 MySQL事务前言面试题什么是数据库事务为什么要有事务呢&#xff1f;项目中遇到的事务事务的传播机制事务的特性&#xff1f;事务并发存在的问题四大隔离级别四大隔离级别&#xff0c;都会存在哪些并发问题呢数据库是如何保证事务的隔离性的呢&#xff1f;如何解决加锁后的…