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

文章目录

  • 简介
  • Spring EL的常见应用
    • @Value
    • 处理注解
    • xml中使用
  • Spring EL表达式
    • 基本表达式
    • 模板
    • 函数表达式
  • Spring EL定制
  • 引用Spring的bean

简介

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

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

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

一个简单的场景:

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

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

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

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

Spring EL的常见应用

@Value

//注入操作系统属性
@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,在设置的时候可以更加灵活。

@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能完成那些操作。

基本表达式

@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));
}

模板

@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,就不能解析多个,只能解析单个变量,如:

@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中还有一个非常有用的就是可以引用函数。

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));}
}

还可以带参数:

@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就可以,那么下面的方式可以行吗?

@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));
}

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

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

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

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

可以利用StandardEvaluationContext的root对象。

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();}
}
@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中所有的类和方法都可以引用,非常灵活。

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);}
}
import org.springframework.stereotype.Service;@Service(value="elService")
public class ELService {public String service(){return "el service";}
}
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.mzph.cn/news/832960.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

子集II(力扣90)

解题思路&#xff1a;used判断是否出现过&#xff0c;然后遍历就好了 具体代码如下&#xff1a; class Solution { public: vector<vector<int>>result; vector<int>path; void travelBack(vector<int>&nums,int startIndex,vector <bool&…

内容安全(DPI和DFI解析)

内容安全前言&#xff1a; 防火墙的本质其实就是包过滤&#xff0c;我们通常所说的安全设备&#xff08;如&#xff1a;IPS、IDS、AV、WAF&#xff09;的检测重心是应用层。下一代防火墙基于传统防火墙的拓展能力&#xff0c;就是可以将以上的安全设备模块集成在一起&#xff0…

智慧应急三维电子沙盘系统

深圳易图讯科技有限公司&#xff08;www.3dgis.top&#xff09;自主研发的智慧应急三维电子沙盘系统依托大数据融合物联网、云计算、移动互联、5G、BIM、三维GIS等新一代信息技术&#xff0c;集成了高清卫星影像、地形数据、实景三维模型、现场环境数据、物联感知信息、人口、建…

Java作业8-泛型和模板类

编程1 题目 自定义堆栈类 用泛型数组实现自定义堆栈类GenericStack&#xff0c;可处理任意类型数据。 堆栈类中包含三个成员&#xff0c;一为泛型类型数组list&#xff0c;保存栈中元素&#xff0c;一为整型成员length表示数组的长度&#xff0c;一为整型成员count表示当前…

SRC公益漏洞挖掘思路分享

0x00 前言 第一次尝试挖SRC的小伙伴可能会觉得挖掘漏洞非常困难&#xff0c;没有思路&#xff0c;不知道从何下手&#xff0c;在这里我分享一下我的思路 0x01 挖掘思路 确定自己要挖的漏洞&#xff0c;以及该漏洞可能存在的功能点&#xff0c;然后针对性的进行信息收集 inurl…

LeetCode 每日一题 ---- 【741.摘樱桃】

LeetCode 每日一题 ---- 【741.摘樱桃】 741.摘樱桃方法&#xff1a;动态规划 741.摘樱桃 方法&#xff1a;动态规划 这是一道动态规划的题目&#xff0c;enmmmm&#xff0c;依旧是做不出来&#xff0c;尤其是看到困难两个标红的字体&#xff0c;就更不想做了&#xff0c;然后…

一起深度学习(AlexNet网络)

AlexNet神经网络 代码实现&#xff1a; 代码实现&#xff1a; import torch from torch import nn from d2l import torch as d2lnet nn.Sequential(# 采用了11*11的卷积核来捕捉对象&#xff0c;因为原始输入数据比较大#步幅为4 &#xff0c;可减少输出的高度核宽度。#输出通…

【算法刷题day41】Leetcode:343. 整数拆分、96. 不同的二叉搜索树

文章目录 Leetcode 343. 整数拆分解题思路代码总结 Leetcode 96. 不同的二叉搜索树解题思路代码总结 草稿图网站 java的Deque Leetcode 343. 整数拆分 题目&#xff1a;343. 整数拆分 解析&#xff1a;代码随想录解析 解题思路 通过两轮循环&#xff0c;分别找到每个数的最大…

FineReport高频面试题及参考答案

FineReport是一款利用什么语言开发的报表工具&#xff1f; FineReport是一款基于Java语言开发的报表工具。Java是一种广泛使用的编程语言&#xff0c;特别适合于跨平台的软件开发。FineReport利用Java语言的诸多优势&#xff0c;如稳定性、安全性、可移植性和强大的网络功能&a…

Compose 状态管理

文章目录 Compose 状态管理概述使用MutableStaterememberStatelessComposable & StatefulComposable状态提升rememberSaveable支持parceable不支持parceable 使用ViewModelViewModelProvider.Factory 使用Flow Compose 状态管理 概述 当应用程序的状态发生变化时&#xf…

(40)4.30数据结构(队列)

1.队列的基本概念 2.队列的顺序 #define MaxSize 10 #define ElemType int typedef struct { ElemType data[MaxSize]; int front, rear; }SqQueue;//1.初始化操作 void InitQueue(SqQueue& Q) { //初始化 队头&#xff0c;队尾指针指向0 Q.rear Q.fron…

环形链表理解||QJ141.环形链表

在链表中&#xff0c;不光只有普通的单链表。之前写过的的一个约瑟夫环形链表是尾直接连向头的。这里的环形链表是从尾节点的next指针连向这链表的任意位置。 那么给定一个链表&#xff0c;判断这个链表是否带环。qj题141.环形链表就是一个这样的题目。 这里的思路是用快慢指…

B3966 [语言月赛 202404] 道法考试

题目背景 你正在参加一场道法考试。这次的道法考试题目全部都是问答题。 题目描述 有 n 道题目&#xff0c;每道题标准答案都是 m 个整数&#xff0c;代表答案包含的知识点的编号。 而你对于第 i&#xff08;1≤i≤n&#xff09;道题的作答是 li​ 个整数&#xff0c;代表作…

jenkins+gitlab+ansible-tower实现发布

前提准备&#xff1a; gitlab中上传相应的jenkinsfile文件和源码。 安装和破解ansible-tower。 安装jenkins。 大致流程&#xff1a;从gitlab中拉取文件&#xff0c;存放到windows机器上&#xff0c;使用nuget等进行打包到windows中&#xff0c;使用sshPublisher语句传输到远程…

使用QRegularExpression从样式表中提取颜色

QRegularExpression 类是 Qt 框架中用于处理正则表达式的类。 首先定义一个函数&#xff08;很多代码都来自chat gtp的生成&#xff0c;不得不说确实很棒&#xff09; from PySide6.QtCore import QRegularExpressiondef find_text_between_strings(text, start_str, end_st…

LLMs之RAG:LangChain-Chatchat(一款中文友好的全流程本地知识库问答应用)的简介(支持 FastChat 接入的ChatGLM-2/LLaMA-2等多款主流LLMs+多款embe

LLMs之RAG&#xff1a;LangChain-Chatchat(一款中文友好的全流程本地知识库问答应用)的简介(支持 FastChat 接入的ChatGLM-2/LLaMA-2等多款主流LLMs多款embedding模型m3e等多种TextSplitter分词器)、安装(镜像部署【AutoDL云平台/Docker镜像】&#xff0c;离线私有部署支持RTX3…

yum仓库及NFS共享

yum简介 yum是一个基于RPM包&#xff08;是Red-Hat Package Manager红帽软件包管理器的缩写&#xff09;构建的软件更新机制&#xff0c;能够自动解决软件包之间的依赖关系。 yum 实现过程 先在yum服务器上创建 yum repository&#xff08;仓库&#xff09;&#xff0c;在仓…

stm32f103zet6_DAC_2_输出电压

实现效果 DAC输出的电压 同过电压表测量电压 1.DAC配置的步骤 初始化DAC时钟。配置DAC的GPIO端口。设置DAC的工作模式&#xff08;例如&#xff0c;是否使用触发功能&#xff0c;是否启用DAC中断等&#xff09;。启动DAC。 2常用的函数 函数 HAL_DAC_Start() - 开启指定…

EtherCAT开发_4_分布时钟知识点摘抄笔记1

分布时钟 (DC&#xff0c;Distributed Cl ock) 可以使所有EtherCAT设备使用相同的系统时间&#xff0c;从而控制各设备任务的同步执行。从站设备可以根据同步的系统时间产生同步信号&#xff0c;用于中断控制或触发数字量输入输出。支持分布式时钟的从站称为 DC 从站。分布时钟…