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

ops/2024/9/24 9:54:26/

文章目录

简介

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/ops/36259.html

相关文章

k8s 资源文件参数介绍

Kubernetes资源文件yaml参数介绍 yaml 介绍 yaml 是一个类似 XML、JSON 的标记性语言。它强调以数据为中心&#xff0c;并不是以标识语言为重点例如 SpringBoot 的配置文件 application.yml 也是一个 yaml 格式的文件 语法格式 通过缩进表示层级关系不能使用tab进行缩进&am…

Windows系统安装MySQL数据库详细教程

【确认本地是否安装mysql】 &#xff08;1&#xff09;按【winr】快捷键打开运行&#xff1b; &#xff08;2&#xff09;输入services.msc&#xff0c;点击【确定】&#xff1b; &#xff08;3&#xff09;在打开的服务列表中查找mysql服务&#xff0c;如果没有mysql服务&am…

element-plus el-time-picker 时间段选择(可多选)

实现一个如图的时间段选择器 处理好时间回显逻辑&#xff0c;组件内[‘’,‘’],后端数据[{startTime:‘’,endTime:‘’}]处理好加和减的显示逻辑 <template><div><div v-for"(item, index) in currentChoose" :key"index" class"fl…

ubuntu20部署3d高斯

3d高斯的链接&#xff1a;https://github.com/graphdeco-inria/gaussian-splatting 系统环境 ubuntu20的系统环境&#xff0c;打算只运行训练的代码&#xff0c;而不去进行麻烦的可视化&#xff0c;可视化直接在windows上用他们预编译好的exe去可视化。&#xff08;因为看的很…

JAVA每日面试题(二)

Java高级面试问题及答案 问题1: 请解释Java内存模型(JMM)及其重要性 答案&#xff1a; Java内存模型&#xff08;JMM&#xff09;是一个抽象的概念&#xff0c;它定义了Java程序中各种变量&#xff08;线程共享变量&#xff09;的访问规则&#xff0c;以及在并发环境下如何保…

30万买智驾车,选特斯拉还是华为?

文 | AUTO芯球 作者 | 雷歌 我真是佩服马斯克&#xff0c; 一趟24小时的北京出差&#xff0c;就解除了Model车系进入机关单位禁令的问题&#xff0c; 也打开了特斯拉FSD完全自动驾驶进入中国市场的大门&#xff0c; 给我我一天时间&#xff0c;估计一部剧都追不完&#xf…

深度学习实战76-基于目标检测YOLOv5模型的迁移学习使用方法,YOLOv5的原理与结构

大家好,我是微学AI,今天给大家介绍一下深度学习实战76-基于目标检测YOLOv5模型的迁移学习使用方法,YOLOv5的原理与结构。YOLOv5(You Only Look Once version 5)是一种先进的目标检测算法,基于深度学习的单阶段目标检测模型。它的主要原理是通过一次前向传播就同时预测图像…

力扣每日一题114:二叉树展开为链表

题目 中等 提示 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同…