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

devtools/2024/9/24 9:54:27/

文章目录

简介

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表达式是运行时解析

KaTeX parse error: Expected 'EOF', got '#' at position 7: 属性表达式比#̲EL表达式先执行,所以可以在E…属性表达式

处理注解

最常见的就是缓存、分布式锁等注解,可以根据方法的注解、结合实际调用的参数,计算缓存的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的表达式默认是#{}

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

例如:

表达式 n a m e − {name}- name{year}年 m o n t h 月 {month}月 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的对象的对应属性,

例如: n a m e 就等价于 {name}就等价于 name就等价于{#root.name}

前面没有设置StandardEvaluationContext的root,root为空,所以 n a m e − {name}- name{year}年 m o n t h 月 {month}月 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/devtools/29549.html

相关文章

Springboot+mybatis升级版(Postman测试)

一、项目结构 1.导入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apach…

SQL——高级教程【菜鸟教程】

SQL连接 左连接&#xff1a;SQL LEFT JOIN 关键字 左表相当于主表&#xff0c;不管与右表匹不匹配都会显示所有数据 右表就只会显示和左表匹配的内容。 //例显示&#xff1a;左表的name&#xff0c;有表的总数&#xff0c;时间 SELECT Websites.name, access_log.count, acc…

VS2022 嘿嘿

还是大二的时候就开始用这个&#xff0c;但居然是为了用PB&#xff0c;-_-|| 用了段时间换成了C#&#xff0c;依稀还记得大佬们纠正我的读法&#xff0c;别读C井&#xff0c;应该读C夏普。。。 安装过程其实也没啥&#xff0c;就是关键Key得花时间找&#xff0c;我好不容易搞…

【跟马少平老师学AI】-【神经网络是怎么实现的】(九)长短期记忆网络

一句话归纳&#xff1a; 1&#xff09;RNN也会存在梯度消失的问题。 2&#xff09;同一句话&#xff0c;对于不同的任务&#xff0c;句中不同的词起的作用也不一样。 3&#xff09;LSTM&#xff08;长短期记忆&#xff09;子网络&#xff1a; 门&#xff0c;让输入经过运算&…

JavaScript+C#云LIS系统源码JQuery+EasyUI+Bootstrap云LIS系统应用于哪些行业领域?区域云LIS系统源码

JavaScriptC&#xff03;云LIS系统源码JQueryEasyUIBootstrap云LIS系统应用于哪些行业领域&#xff1f;区域云LIS系统源码 云LIS是为区域医疗提供临床实验室信息服务的计算机应用程序&#xff0c;可协助区域内所有临床实验室相互协调并完成日常检验工作&#xff0c;对区域内的检…

Linux内存图

简化的Linux内存布局图&#xff0c;使用文本线条表示 内核空间存储了操作系统的核心组件&#xff0c;包括系统调用处理、硬件抽象层、驱动程序等。 ----------------- <-- 内核空间开始 (虚拟内存的顶部) | 内核代码 | -----------------------------------> 内…

《QT实用小工具·四十八》趣味开关

1、概述 源码放在文章末尾 该项目实现了各种样式的趣味开关&#xff1a; 1、爱心形状的switch开关&#xff0c;支持手势拖动、按压效果 2、线条样式的3种开关 项目demo演示如下所示&#xff1a; 使用方式&#xff1a; 1、sapid_switch文件夹加入工程&#xff0c;.pro文件中…

微软开源 MS-DOS「GitHub 热点速览」

上周又是被「大模型」霸榜的一周&#xff0c;各种 AI、LLM、ChatGPT、Sora、RAG 的开源项目在 GitHub 上“争相斗艳”。这不 Meta 刚开源 Llama 3 没几天&#xff0c;苹果紧跟着就开源了手机端大模型&#xff1a;CoreNet。 GitHub 地址&#xff1a;github.com/apple/corenet 开…