Spring EL 表达式的简单介绍和使用

本文深入介绍了Spring Expression Language (SpEL),包括其基本概念、使用场景、如何使用以及示例代码。SpEL提供了一种强大且灵活的方式来装配Bean,动态计算值,并访问和操作Spring容器中的Bean属性。文章通过多个示例展示了SpEL在字符串操作、数学运算、逻辑判断、集合操作等方面的应用,并讨论了其优缺点。
摘要由CSDN通过智能技术生成


本文主体部分来自于 KILLKISSSpringEL详解及应用。对文中部分代码做了校验和补充,并添加了自己的部分代码。


1. 简单介绍

1.1. 什么是 Spring EL

Spring3 中引入了 Spring 表达式语言 — Spring EL,SpEL 是一种强大,简洁的装配 Bean 的方式,它可以通过运行期间执行的表达式将值装配到我们的属性或构造函数当中,更可以调用 JDK 中提供的静态常量,获取外部 Properties 文件中的的配置。

1.2. 为什么要使用 Spring EL

我们平常通过配置文件或注解注入的 Bean,其实都可以称为静态性注入,试想一下,如果我的 Bean A 中有变量 A,它的值需要根据 Bean B 的 B 变量为参考,在这个场景下静态注入就显得非常无力,而 Spring3 增加的 Spring EL 就可以完全满足这种需求,而且还可以对不同 Bean 的字段进行计算再进行赋值,功能非常强大。

1.3. 如何使用 Spring EL

Spring EL 从名字来看就能看出,和 EL 是有关系的,Spring EL 的使用和 EL 表达式的使用非常相似,EL 表达式在 JSP 页面更方便的获取后台中的值,而 Spring EL 就是为了更方便获取 Spring 容器中的 Bean 的值,EL 使用${},而 Spring EL 使用#{}进行表达式的声明。

2. 简单使用

辅助类:

package com.example.spel.bean;

import org.springframework.stereotype.Component;
import java.util.*;

@Component
public class TestConstant {
    public static final String STR = "测试SpEL";
    public String nickname = "一线大码";
    public String name = "笑傲江湖";
    public int num = 5;
    public List<String> testList = Arrays.asList("aaa", "bbb", "ccc");
    public Map testMap = new HashMap() {{
        put("aaa", "元宇宙算法");
        put("hello", "world");
    }};
    public List cityList = new ArrayList<City>() {{
        add(new City("aaa", 500));
        add(new City("bbb", 600));
        add(new City("ccc", 1000));
        add(new City("ddd", 1000));
        add(new City("eee", 2000));
        add(new City("fff", 3000));
    }};

    public String showProperty() {
        return "Hello";
    }

    public String showProperty(String str) {
        return "Hello " + str + "!";
    }
}
package com.example.spel.bean;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class City {

    private String name;
    private long population;
}

测试代码:

package com.example.spel.el;

import com.example.spel.bean.City;
import com.example.spel.bean.TestConstant;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.List;

@ToString
@Component
public class TestSpringEL {

    /**
     * 注入简单值,输出num为5
     */
    @Value("#{5}")
    private Integer num;

    @Value("#{'rain'.toUpperCase()}")
    private String name;

    //注入bean,访问属性和方法

    /**
     * 注入ID为testConstant的Bean
     */
    @Value("#{testConstant}")
    private TestConstant testConstant;

    /**
     * 注入ID为testConstant的Bean中的STR常量/变量
     */
    @Value("#{testConstant.STR}")
    private String str;

    /**
     * 调用无参方法
     */
    @Value("#{testConstant.showProperty()}")
    private String method1;

    /**
     * 调用有参方法,接收字符串
     */
    @Value("#{testConstant.showProperty('World')}")
    private String method2;

    /**
     * 方法返回的String为大写
     */
    @Value("#{testConstant.showProperty().toUpperCase()}")
    private String method3;

    /**
     * 使用method3这种方式,如果showProperty返回为null,将会抛出NullPointerException,可以使用以下方式避免。
     * 使用?.符号表示如果左边的值为null,将不执行右边方法
     */
    @Value("#{testConstant.showProperty()?.toUpperCase}")
    private String method4;

    //注入JDK中的工具类常量或调用工具类的方法

    /**
     * 获取Math的PI常量
     */
    @Value("#{T(java.lang.Math).PI}")
    private double pi;

    /**
     * 调用random方法获取返回值
     */
    @Value("#{T(java.lang.Math).random()}")
    private double ramdom;

    /**
     * 获取文件路径符号
     */
    @Value("#{T(java.io.File).separator}")
    private String separator;

    //使用SpringEL进行运算及逻辑操作

    /**
     * 拼接字符串
     */
    @Value("#{testConstant.nickname + ' ' + testConstant.name}")
    private String concatString;

    /**
     * 对数字类型进行运算
     */
    @Value("#{ 3 * T(java.lang.Math).PI + testConstant.num}")
    private double operation;

    /**
     * 进行逻辑运算
     */
    @Value("#{testConstant.num > 100 and testConstant.num <= 200}")
    private boolean logicOperation;

    /**
     * 进行或非逻辑操作
     */
    @Value("#{not(testConstant.num == 100 or testConstant.num <= 200)}")
    private Boolean logicOperation2;

    /**
     * 使用三元运算符
     */
    @Value("#{testConstant.num > 100 ? testConstant.num : testConstant.num + 100}")
    private Integer logicOperation3;

    //SpringEL使用正则表达式

    /**
     * 验证是否邮箱地址正则表达式
     */
    @Value("#{testConstant.STR matches '\\w+([\\.-]?\\w+)*@\\w+([\\.-]?\\w+)*(\\.\\w{2,3})+'}")
    private boolean regularExpression;

    //TestConstant类中有名为testList的List变量,和名为testMap的Map变量

    /**
     * 获取下标为0的元素
     */
    @Value("#{testConstant.testList[0]}")
    private String firstStr;

    /**
     * 获取下标为0元素的大写形式
     */
    @Value("#{testConstant.testList[0]?.toUpperCase()}")
    private String upperFirstStr;

    /**
     * 获取map中key为hello的value
     */
    @Value("#{testConstant.testMap['hello']}")
    private String mapValue;

    /**
     * 根据testList下标为0元素作为key获取testMap的value
     */
    @Value("#{testConstant.testMap[testConstant.testList[0]]}")
    private String mapValueByTestList;

    //声明City类,有population(人口)属性。testConstant拥有名为cityList的City类List集合

    /**
     * 过滤testConstant中cityList集合population属性大于1000的全部数据注入到本属性
     */
    @Value("#{testConstant.cityList.?[population > 1000]}")
    private List<City> cityList;

    /**
     * 过滤testConstant中cityList集合population属性等于1000的第一条数据注入到本属性
     */
    @Value("#{testConstant.cityList.^[population == 1000]}")
    private City city;

    /**
     * 过滤testConstant中cityList集合population属性小于1000的最后一条数据注入到本属性
     */
    @Value("#{testConstant.cityList.$[population < 1000]}")
    private City city2;

    /*
     * 首先为city增加name属性,代表城市的名称
     */

    /**
     * 假如我们在过滤城市集合后只想保留城市的名称,可以使用如下方式进行投影
     */
    @Value("#{testConstant.cityList.?[population > 1000].![name]}")
    private List<String> cityName;
}

执行测试:

package com.example.spel;

import com.example.spel.el.TestSpringEL;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * @author wangbo
 * @date 2021/11/26
 */
@SpringBootTest
class Test1 {

    @Autowired
    private TestSpringEL testSpringEL;

    @Test
    void test(){
        System.out.println(testSpringEL);
    }
}

测试结果:

TestSpringEL(num=5, name=rain, name1=RAIN, testConstant=com.example.spel.bean.TestConstant@35cd68d4, str=测试SpEL, method1=Hello, method2=Hello World!, method3=HELLO, method4=HELLO, pi=3.141592653589793, ramdom=0.38512203414759527, separator=\, concatString=一线大码 笑傲江湖, operation=14.42477796076938, logicOperation=false, logicOperation2=false, logicOperation3=105, regularExpression=false, firstStr=aaa, upperFirstStr=AAA, mapValue=world, mapValueByTestList=元宇宙算法, cityList=[City(name=eee, population=2000), City(name=fff, population=3000)], city=City(name=ccc, population=1000), city2=City(name=bbb, population=600), cityName=[eee, fff])

3. EL 表达式解析引擎

3.1. 带缓存的 EL 表达式工具方法:

package gtcom.governance.impl.util;

import org.apache.commons.lang3.StringUtils;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * SpEL表达式解析
 *
 * @author wangbo
 * @date 2021/11/26
 */
public class ExpressionUtils {

    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();

    private static final Map<String, Expression> EXPRESSION_CACHE = new ConcurrentHashMap<>();

    /**
     * 获取解析后的表达式
     *
     * @param expression EL表达式字符串
     * @return 解析后的表达式,如果之前已经解析过,则返回缓存的表达式
     */
    public static Expression getExpression(String expression) {
        if (StringUtils.isBlank(expression)) {
            return null;
        }
        expression = expression.trim();
        return EXPRESSION_CACHE.computeIfAbsent(expression, EXPRESSION_PARSER::parseExpression);
    }
}

上面工具的测试代码:

package gtcom.governance.impl.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.expression.Expression;

import java.util.Objects;

/**
 * 测试SpEL表达式解析
 *
 * @author wangbo
 * @date 2021/11/26
 */
public class TestExpression {

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode root = mapper.createObjectNode();
        root.put("pubTime", "11111");
        ObjectNode supplier = root.putObject("supplier");
        supplier.put("sourceType", "TestSourceType");
        supplier.put("comFrom", "QX");

        System.out.println(mapper.writeValueAsString(root));

        String sourceType = root.get("supplier").path("sourceType").asText();
        System.out.println(sourceType);

        String expression1 = "get('supplier').path('sourceType').asText";
        Expression expr1 = ExpressionUtils.getExpression(expression1);
        System.out.println(expr1);
        String sourceType1 = Objects.requireNonNull(expr1).getValue(root, String.class);
        System.out.println(sourceType1);

        String expression2 = "get('supplier').path('sourceType').asText";
        Expression expr2 = ExpressionUtils.getExpression(expression2);
        System.out.println(expr2);
        String sourceType2 = Objects.requireNonNull(expr2).getValue(root, String.class);
        System.out.println(sourceType2);
    }
}

3.2. 设置上下文的 EL 表达式

package com.example.spel;

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class TestStringSubExpression {
    public static void main(String[] args) {
        String expressionStr = "'hello world'.toUpperCase().substring(1,5)";
        //指定SpelExpressionParser解析器实现类
        ExpressionParser parser = new SpelExpressionParser();
        //解析表达式
        Expression expression = parser.parseExpression(expressionStr);
        System.out.println(expression.getValue());
    }
}

等价设置了上下文的代码:

package com.example.spel;

import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class TestStringSubExpression {
    public static void main(String[] args) {
        String expressionStr = "'hello world'.toUpperCase().substring(#start, #end)";
        //指定SpelExpressionParser解析器实现类
        ExpressionParser parser = new SpelExpressionParser();
        //解析表达式
        Expression expression = parser.parseExpression(expressionStr);
        //设置对象模型基础
        EvaluationContext context = new StandardEvaluationContext();
        context.setVariable("start", 1);
        context.setVariable("end", 5);
        System.out.println(expression.getValue(context));
    }
}

另一个设置了上下文的 SpEL 测试代码:

package com.example.spel;

import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class Test {
    public static void main(String[] args) {
        //测试SpringEL解析器。
        //设置文字模板,其中#{}表示表达式的起止,#user是表达式字符串,表示引用一个变量。
        String template = "#{#user},早上好";
        //创建表达式解析器。
        ExpressionParser parser = new SpelExpressionParser();

        //通过evaluationContext.setVariable可以在上下文中设定变量。
        EvaluationContext context = new StandardEvaluationContext();
        context.setVariable("user", "黎明");

        //解析表达式,如果表达式是一个模板表达式,需要为解析传入模板解析器上下文。
        Expression expression = parser.parseExpression(template, new TemplateParserContext());

        //使用Expression.getValue()获取表达式的值,这里传入了Evaluation上下文,第二个参数是类型参数,表示返回值的类型。
        System.out.println(expression.getValue(context, String.class));
    }
}

代码执行结果:

黎明,早上好

4. 总结

优点:

Spring EL 功能非常强大,在 Annotation 的方式开发时可能感觉并不强烈,因为可以直接编写到源代码来实现 Spring EL 的功能,但如果是在 XML 文件中进行配置,Spring EL 可以弥补 XML 静态注入的不足,从而实现更强大的注入。

缺点:

Spring EL 在使用时仅仅是一个字符串,不易于排错与测试,也没有 IDE 检查我们的语法(目前 DIEA 可以检测 EL 语言),当出现错误时较难检测。

笔者实际应用:

笔者开发的项目当中比较频繁的使用 Spring EL,例如通过 Spring EL 获取外部 properties 中的值,又或者项目当中的数据字典亦是使用 Spring EL 的一个场景,我们抽象出一个 Param 类的集合,通过 Spring EL 集合筛选和投影获取我们想要的字段参数添加到我们的程序逻辑当中(笔者项目中的 Spring Security 亦使用 Spring EL,但本文章不加以叙述)。

总结:

Spring3.0 让人为之惊艳的非 Spring EL 莫属,为我们的注入提供了另一种强大的形式,传统注入能做到的事情,和做不到的事情,Spring EL 一概能完成,但在项目当中并不适宜大量使用 Spring EL,适当的技术方在适当的位置,才能更好的完成事情。

5. 补充示例

@Value中的${...}表示占位符,它会读取上下文的属性值装配到属性中。
@Value中的#{...}表示启用 Spring 表达式,具有运算功能。
T(...)表示引入类,Systemjava.lang.*包下的类,是 Java 默认加载的包,因此可以不用写全限定名,如果是其它的包,则需要写出全限定名才能引用类。

@Value("${database.driverName}")
private String driver;

@Value("#{T(System).currentTimeMillis()}")
private Long initTime = null;

5.1. 使用 Spring EL 进行赋值

//赋值字符串
@Value("#{'使用 Spring EL 赋值字符串'}")
private String str = null;

//科学计数法赋值
@Value("#{9.3E3}")
private double d;

//赋值浮点数
@Value("#{3.14}")
private float pi;
//赋值 bean 的属性
@Value("#{beanName.str}")
private String otherBeanProp = null;

//?表示判断是否为 null,不为空才会执行后面的表达式
@Value("#{beanName.str?.toUpperCase()}")
private String otherBeanProp1 = null;

5.2. 使用 Spring EL 进行计算

//数学运算
@Value("#{1+2}")
private int run;

//浮点数比较运算
@Value("#{beanName.pi == 3.14f}")
private boolean piFlag;

//字符串比较运算
@Value("#{beanName.str eq 'Spring Boot'}")
private boolean strFlag;

//字符串连接
@Value("#{beanName.str + ' 连接字符串'}")
private String strApp = null;

//三元运算
@Value("#{beanName.d > 1000 ? '大于' : '小于'}")
private String resultDesc = null;
  • 9
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring框架中使用EL表达式(Expression Language)可以方便地访问和操作Java对象,主要用于在JSP页面或Spring的注解中进行数据绑定和表达式求值。 以下是在Spring使用EL表达式的几个常见场景和用法: 1. 在JSP页面中使用EL表达式: 在JSP页面中,可以使用EL表达式访问和操作Java对象的属性、方法和集合。例如,`${user.name}`可以获取名为user的Java对象的name属性值。 2. 在Spring MVC的注解中使用EL表达式: 在Spring MVC中,可以使用EL表达式在注解中动态地设置属性值。例如,`@RequestMapping("${url.mapping}")`可以根据EL表达式`${url.mapping}`的值动态地设置请求映射路径。 3. 在Spring的XML配置文件中使用EL表达式: 在Spring的配置文件中,可以使用EL表达式引用其他属性或bean的值。例如,`<property name="timeout" value="${connection.timeout}"/>`可以将`${connection.timeout}`的值设置为bean的timeout属性。 4. 使用Spring表达式语言(SpEL): Spring框架还提供了一种更强大的EL表达式语言,称为SpELSpring Expression Language)。SpEL支持更复杂的表达式求值和操作,可以在Spring的注解、XML配置文件以及运行时动态注入等场景中使用。 需要注意的是,在使用EL表达式时,需要确保相关的JAR包已经添加到项目的依赖中,通常是`javax.el-api`和`jstl`。 希望以上信息对你有所帮助!如果还有其他问题,请继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值