基于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]);
        }

        // 计算key
        String 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;
    }

    @Test
    public 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季报.xlsx
    System.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@.xlsx
    System.out.println(expression.getValue(context, String.class));

    template = "map-${paramMap['p1']}-${paramMap['p2']}.xlsx";
    expression = parser.parseExpression(template, parserContext);
    // map-pa-pb.xlsx
    System.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;

    @Override
    public 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 {

    @Resource
    private ApplicationContextHolder applicationContextHolder;

    @Test
    public 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.mfbz.cn/a/594563.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

天软特色因子看板 (2024.4 第8期)

该因子看板跟踪天软特色因子A05005(近一月单笔流出金额占比(%)&#xff0c;该因子为近一月单笔流出金额占比(% 均值因子&#xff0c;用以刻画下跌时的 单成交中可能存在的抄底现象 今日为该因子跟踪第8期&#xff0c;跟踪其在SW801080 (申万电子) 中的表现&#xff0c;要点如下…

Java 对象创建过程十步法!你get到了吗?

Java 中对象的创建过程可以概括为十个步骤&#xff0c;从类加载到实例化对象。 下面详细讲解一下每个步骤&#xff1a; 1. 类加载&#xff1a; Java 虚拟机在加载类时&#xff0c;会检查类的字节码&#xff0c;并将其加载到内存中。类加载的过程包括加载、连接&#xff08;验…

Amine-PEG-Amine,956496-54-1在生物成像、生物传感器等领域具有广泛的应用

【试剂详情】 英文名称 Amine-PEG-Amine&#xff0c;NH2-PEG-NH2 中文名称 氨基-聚乙二醇-氨基&#xff0c;氨基PEG氨基&#xff0c; 双端氨基聚乙二醇 CAS号 956496-54-1 外观性状 由分子量决定&#xff0c;液体或者固体 分子量 0.4k&#xff0c;0.6k&#xff0c;1k&…

【JAVA |开篇】JAVA入门及JDK环境配置

目录 一、JIAVA语言 二、Java开发环境安装 三、初识Java的main方法 四、注释 一、JIAVA语言 Java 是一种优秀的程序设计语言 &#xff0c;它具有令人赏心悦目的语法和易于理解的语义 Write once, Run anywhere&#xff08;这句话体现了JAVA语言的核心&#xff0c;一次运行 任…

Vue从入门到精通-14-Vue组件

子组件的定义和注册 我们在本文的第一段中&#xff0c;通过Vue.component形式定义的是全局组件。这一段中&#xff0c;我们来讲一下子组件。 在父组件中定义子组件 比如说&#xff0c;一个账号模块是父组件&#xff0c;里面分为登陆模块和注册模块&#xff0c;这两个晓得模块…

ubuntu中的docker记录(3)——如何安装nvidia-docker以更好地支持GPU加速计算应用程序的运行

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、nvidia-docker2的安装1. 安装docker2. 安装nvidia-docker2(1) 添加密钥(2) 更新软件列表(3) 安装nvidia-docker2(4) 测试nvidia-docker2 二、可能的报错及解…

专家解读 | NIST网络安全框架(1):框架概览

随 着信息技术的快速发展&#xff0c;组织面临着越来越严峻的网络安全挑战。NIST网络安全框架&#xff08;NIST Cybersecurity Framework&#xff0c;CSF&#xff09;是一个灵活的综合性指南&#xff0c;旨在协助各类组织建立、改进和管理网络安全策略&#xff0c;以加强网络安…

leetCode81. 搜索旋转排序数组 II

leetCode81. 搜索旋转排序数组 II 题目思路 可以二分后的具体思路见我的上篇博客 搜索旋转排序数组 代码 class Solution { public:bool search(vector<int>& nums, int target) {if(nums.empty()) return false;int R nums.size() - 1;while(R > 0 &&…

LLMs:《Better Faster Large Language Models via Multi-token Prediction》翻译与解读

LLMs&#xff1a;《Better & Faster Large Language Models via Multi-token Prediction》翻译与解读 目录 《Better & Faster Large Language Models via Multi-token Prediction》翻译与解读 Abstract 2、Method方法 Memory-efficient implementation 高效内存实…

LabVIEW数据库访问技术

LabVIEW数据库访问技术 在当前的信息化时代&#xff0c;数据管理与分析在各个领域中起着重要的作用。特别是在工业、科研等领域&#xff0c;对于数据的快速准确获取、处理和分析需求日益增加。LabVIEW作为一种图形化编程语言&#xff0c;以其直观、高效的特点&#xff0c;在自…

【数据分析】这些年我发过的微信朋友圈

TencentRecordAnalysisV1.0.3.zip 蓝奏云&#xff1a;链接:链接TencentRecordAnalysis (lanzoub.com)密码:9hww 朋友圈还是以本行业岩土、工作相关的内容居多。 对于一个不怎么发圈的人来说&#xff0c;这几天有点反常&#xff0c;这几天大概是我成功的开发了几个失败的GPT应用…

打造亚马逊爆款秘诀:流量、排名与自养号测评的完美结合

亚马逊是一个产品为王的平台&#xff0c;只要我们的产品好&#xff0c;就会有更多的流量&#xff0c;有流量还怕我们的产品卖不出去&#xff1f;身为新手我们店无流量该怎么办&#xff0c;今天教给你们五个获取流量的方法。 1.自然检索 那是我们常说的自然流量&#xff0c;通…

DBCHM 数据库 CHM 文档生成工具

介绍 DBCHM 是一款数据库文档生成工具&#xff01; 该工具从最初支持chm文档格式开始&#xff0c;通过开源&#xff0c;集思广益&#xff0c;不断改进&#xff0c;又陆续支持word、excel、pdf、html、xml、markdown等文档格式的导出。 支持的数据库 SqlServerMySQLOraclePos…

拥抱新质生产力,助力新型工业化!CMM电子展暨IARS机器人展5月东莞盛大起航

2024年5月15-17日&#xff0c;东浩兰生会展集团旗下CMM电子展&#xff06;IARS机器人展将在广东现代国际展览中心&#xff08;东莞厚街&#xff09;举办。展会面积达50000平方米&#xff0c;展示品牌700余个&#xff0c;同期论坛峰会30余场&#xff0c;预计专业观众超50000人次…

肆拾玖坊商业模式分析,新品牌如何采用合伙人模式起盘

坐标&#xff1a;厦门&#xff0c;我是易创客运营肖琳 深耕社交新零售行业10年&#xff0c;主要提供新零售系统工具及顶层商业模式设计、全案策划运营陪跑等。 比茅台盈利模式还牛逼的肆拾玖坊&#xff0c;所有男人都逃不出它的圈套&#xff01;只靠49个男人&#xff0c;用一套…

软考信息系统项目管理师论文突然单独考,其实影响没有想象的大

五一假期的前一天&#xff0c;辽宁省软考办发布了一则通知&#xff0c;安排了辽宁省的软考批次安排&#xff0c;从标题看不出来有用的信息&#xff0c;但是干货是埋在正文中的。我先把辽宁软考办的全文给你附上如下&#xff0c;具体的解读后面我会一一道来。 敲重点来了&#x…

笔记13-OSError: [Errno 24] Too many open files

文章目录 参考文献失败尝试系列查看发现&#xff0c;似乎是因为线程数有限制 修改配置先查查看 增加文件数限制&#xff0c;然后使用命令运行&#xff08;成功&#xff09; 参考文献 Linux 最大可以打开多少文件描述符&#xff1f; OSError: [Errno 24] Too many open files错…

解决在C#中方向键对控件焦点的控制

不要犹豫直接把下面这个程序复制进去就好了&#xff0c;不用担心0个引用&#xff0c;哈哈&#xff0c;可以的 public partial class MainForm : Form {public MainForm(){InitializeComponent();}protected override bool ProcessDialogKey(Keys keyData){// 检查是否是方向键…

基于springboot实现实习管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现实习管理系统演示 摘要 近年来&#xff0c;信息化管理行业的不断兴起&#xff0c;使得人们的日常生活越来越离不开计算机和互联网技术。首先&#xff0c;根据收集到的用户需求分析&#xff0c;对设计系统有一个初步的认识与了解&#xff0c;确定实习管理系统…
最新文章