Spring EL 表达式

Spring EL 表达式(Spring Expression Language,简称 SpEL)是 Spring 框架中一个非常强大的表达式语言,用来在运行时动态地查询和操作对象。

SpEL 是 Spring Core 自带的功能,位于包:org.springframework.expression。不需要添加额外的依赖。

基础使用

1、字符串处理

java 复制代码
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello ' + 'Spring EL!'");
String result = exp.getValue(String.class);
System.out.println(result);//输出Hello Spring EL!

//调用字符串方法
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
System.out.println(exp.getValue());

在 SpEL中,单引号 ' 用于 定义字符串常量,类似于 Java 中的字符串字面量。当你想在表达式中表达包含 单引号的字符时,你需要双重单引号('')来转义。

2、访问java对象

复制代码
Person person = new Person("cpx", 9);
Expression exp = parser.parseExpression("name + '今年' + age + '岁'");
System.out.println(exp.getValue(person));  // 输出:cpx今年9岁

3、带上下文变量

java 复制代码
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();

Map<String, Object> vars = new HashMap<>();
vars.put("name", "cpx");
vars.put("age", 9);
context.setVariables(vars);

String tpl = "'尊敬的' + #name + ',您今年' + #age + '岁。'";
String result = parser.parseExpression(tpl).getValue(context, String.class);
//输出:尊敬的cpx,您今年9岁。
System.out.println(result);

4、使用spring容器中bean

java 复制代码
@Autowired
private ApplicationContext context;

public void testEl() {
    ExpressionParser parser = new SpelExpressionParser();
    StandardEvaluationContext ctx = new StandardEvaluationContext();
    ctx.setBeanResolver(new BeanFactoryResolver(context));

    String result = parser.parseExpression("@myService.getMessage()").getValue(ctx, String.class);
    System.out.println(result);
}

5、表达式运算

java 复制代码
ExpressionParser parser = new SpelExpressionParser();
// 加法
Expression exp = parser.parseExpression("5 + 3");
int result = exp.getValue(Integer.class);
System.out.println(result);  // 输出:8

// 乘法
exp = parser.parseExpression("5 * 3");
result = exp.getValue(Integer.class);
System.out.println(result);  // 输出:15
//比较
Expression exp1 = parser.parseExpression("5 > 3");
boolean result1 = exp1.getValue(Boolean.class);
System.out.println(result1);  // 输出:true
// 等于
Expression exp3 = parser.parseExpression("5 == 5");
boolean result3 = exp3.getValue(Boolean.class);
System.out.println(result3);  // 输出:true

加减乘除运算、大于、小于、等于、不等于运算都支持。

6、逻辑运算

java 复制代码
ExpressionParser parser = new SpelExpressionParser();
// AND 运算
Expression exp1 = parser.parseExpression("true and false");
boolean result1 = exp1.getValue(Boolean.class);
System.out.println(result1);  // 输出:false
// OR 运算
Expression exp2 = parser.parseExpression("true or false");
boolean result2 = exp2.getValue(Boolean.class);
System.out.println(result2);  // 输出:true
// NOT 运算
Expression exp3 = parser.parseExpression("not true");
boolean result3 = exp3.getValue(Boolean.class);
System.out.println(result3);  // 输出:false
// 三元运算符
Expression exp = parser.parseExpression("5 > 3 ? 'Yes' : 'No'");
String result = exp.getValue(String.class);
System.out.println(result);  // 输出:Yes

7、调用类方法

复制代码
// 调用方法
Expression exp1 = parser.parseExpression("getName()");
//调用person.getName()方法
String result1 = exp1.getValue(person, String.class);
System.out.println(result1);  
// 调用静态方法
Expression exp = parser.parseExpression("T(java.lang.Math).max(5, 10)");
int result = exp.getValue(Integer.class);
System.out.println(result);  // 输出:10

8、访问集合元素

java 复制代码
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
ExpressionParser parser = new SpelExpressionParser();

// 访问集合的第一个元素
Expression exp = parser.parseExpression("get(0)");
int result = exp.getValue(list, Integer.class);
System.out.println(result);  // 输出:1

其中list使用list.get(index),map使用map['key']

@Value注解

EL表达式经常结合@Value注解来使用。#{表达式}# 开头表示这是一个 SpEL 表达式。表达式里可以包含变量、常量、方法调用、逻辑运算、算术运算等。

场景 示例
读取 bean 属性 @Value("#{user.name}")
调用方法 @Value("#{user.getAge()}")
调用静态方法 @Value("#{T(java.lang.Math).random()}")
计算表达式 @Value("#{1 + 2 * 3}")
条件判断 @Value("#{user.age > 18 ? '成年人' : '未成年人'}")
集合操作 @Value("#{list[0]}")@Value("#{map['key']}")
创建对象 @Value("#{new java.util.Date()}")

AutowiredAnnotationBeanPostProcessor这个beanPostProcessor会负责扫描@Value注解,扫描结果会被封装成InjectionMetadata,随后会调用metadata.inject(bean, beanName, pvs)来进行表达式解析赋值。每一个@Value属性会被封装成AutowiredFieldElement对象,metadata.inject()方法会循环调用AutowiredFieldElement.inject方法来获取每个属性值。

AutowiredFieldElement是AutowiredAnnotationBeanPostProcessor的一个内部类,其inject()方法会调用该beanPostProcessor的resolveFieldValue()方法,最后会调用BeanFacotry.doResolveDependency()来解析依赖值,默认的beanFacotry是DefaultListableBeanFactory类型。

doResolveDependency()方法首先使用QualifierAnnotationAutowireCandidateResolver.getSuggestedValue()来判断是否有@Value注解,有的话返回@Value注解的value属性值。拿到属性值使用AbstractBeanFactory#resolveEmbeddedValue()来从spring 容器配置中去查找value表达式的值。Spring 会遍历所有的 StringValueResolver,去解析值。

AbstractBeanFactory#resolveEmbeddedValue()

java 复制代码
	public String resolveEmbeddedValue(@Nullable String value) {
		if (value == null) {
			return null;
		}
		String result = value;
		for (StringValueResolver resolver : this.embeddedValueResolvers) {
			result = resolver.resolveStringValue(result);
			if (result == null) {
				return null;
			}
		}
		return result;
	}

StringValueResolver是一个@FunctionalInterface,只有一个接口方法。

springboot PropertyPlaceholderAutoConfiguration会自动装配一个PropertySourcesPlaceholderConfigurer。该bean实现了BeanFactoryPostProcessor接口。其postProcessBeanFactory()方法会调用processProperties()方法来初始化StringValueResolver

PropertySourcesPlaceholderConfigurer#processProperties()

java 复制代码
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
		final ConfigurablePropertyResolver propertyResolver) throws BeansException {
	//这里的propertyResolver实例类型是PropertySourcesPropertyResolver
	propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
	propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
	propertyResolver.setValueSeparator(this.valueSeparator);
	//根据ignoreUnresolvablePlaceholders配置来生成Resolver
	StringValueResolver valueResolver = strVal -> {
		String resolved = (this.ignoreUnresolvablePlaceholders ?
				propertyResolver.resolvePlaceholders(strVal) :
				propertyResolver.resolveRequiredPlaceholders(strVal));
		if (this.trimValues) {
			resolved = resolved.trim();
		}
		return (resolved.equals(this.nullValue) ? null : resolved);
	};
	//将StringValueResolver添加到BeanFacotry中
	doProcessProperties(beanFactoryToProcess, valueResolver);
}

PlaceholderResolvingStringValueResolver是PropertySourcesPlaceholderConfigurer的一个内部类。

其resolvePlaceholders()方法通过PropertyPlaceholderHelper从容器配置(properties文件、系统环境变量、命令行参数等地方取值替换)中读取value变量代表的值。这里解析的是${}表达的参数。

如果value是使用#{}表达式表示的bean属性或计算信息,会继续使用StandardBeanExpressionResolver来解析对应的EL表达式值。

自定义标签前后缀

这里Resolver就是使用的SpelExpressionParser来进行EL表达式的解析。StandardBeanExpressionResolver 是在Spring 容器启动阶段由 AbstractApplicationContext 自动创建的。

AbstractApplicationContext#prepareBeanFactory()

java 复制代码
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	if (!shouldIgnoreSpel) {
		beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
	}
}

shouldIgnoreSpel对应spring.spel.ignore配置。如果想自定义BeanExpressionResolver可以通过自定义一个StandardBeanExpressionResolver bean来说实现。StandardBeanExpressionResolver 提供了两个可配置属性:

java 复制代码
private String expressionPrefix = "#{";
private String expressionSuffix = "}";

可以通过以下方式进行设置

java 复制代码
@Configuration
public class MyExpressionConfig {

    @Bean
    public static BeanFactoryPostProcessor customBeanExpressionResolver() {
        return beanFactory -> {
            if (beanFactory instanceof ConfigurableListableBeanFactory) {
                StandardBeanExpressionResolver resolver = new StandardBeanExpressionResolver();
                resolver.setExpressionPrefix("{{");
                resolver.setExpressionSuffix("}}");
                ((ConfigurableListableBeanFactory) beanFactory).setBeanExpressionResolver(resolver);
            }
        };
    }
}

这样就可以使用{{express}}

java 复制代码
@Value("{{2 + 3}}")
private int value;  // 输出 5

另外也可以通过setExpressionParser(ExpressionParser expressionParser)来设置ExpressionParser。

PropertySourcesPlaceholderConfigurer 仍然负责 ${} 的解析;这个类不受上面配置影响。如果你把前缀改成 {``{ }},那原来的 #{} 就不会再被识别。自定义 Resolver 一定要在 容器刷新前 注册(所以用 BeanFactoryPostProcessor)。

SpEL 功能强大,但有时 不应直接执行用户输入的表达式 ,否则可能导致安全风险(例如远程执行代码)。

在使用时要确保表达式来源安全。

相关推荐
沐浴露z2 小时前
详解JDK21新特性【虚拟线程】
java·开发语言·jvm
No0d1es2 小时前
电子学会青少年软件编程(C/C++)1级等级考试真题试卷(2025年9月)
java·c语言·c++·青少年编程·电子学会·真题·一级
9号达人3 小时前
普通公司对账系统的现实困境与解决方案
java·后端·面试
超级苦力怕3 小时前
Java 为何 long a = 999999999 能过;long a = 9999999999 报错?一文讲透“宽化转换”
java
佐杰3 小时前
Jenkins使用指南1
java·运维·jenkins
dllxhcjla3 小时前
三大特性+盒子模型
java·前端·css
Acrelhuang3 小时前
筑牢用电防线:Acrel-1000 自动化系统赋能 35kV 园区高效供电-安科瑞黄安南
java·大数据·开发语言·人工智能·物联网
脸大是真的好~3 小时前
黑马JAVAWeb-10 文件上传-文件存储到服务器本地磁盘-文件存储在阿里云OSS-@Value属性注入
java
大G的笔记本3 小时前
算法篇常见面试题清单
java·算法·排序算法