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

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

相关推荐
半旧夜夏25 分钟前
【保姆级】微服务组件环境搭建(Docker Compose版)
java·linux·spring cloud·微服务·云原生·容器
云烟成雨TD1 小时前
Spring AI 1.x 系列【33】RAG Advisor 组件与四大分层架构
java·人工智能·spring
J2虾虾2 小时前
Spring AI Alibaba - Tools
服务器·人工智能·spring
江南十四行2 小时前
并发编程(七)
java
亦暖筑序3 小时前
单模型成本高、风险大?Spring AI多模型路由实战:成本降70%,可用性更稳
java·后端·ai编程
404号扳手3 小时前
Java 进阶知识(二)
java·后端
SamDeepThinking3 小时前
一个业务场景只需要一个ThreadLocal实例
java·后端·程序员
带刺的坐椅3 小时前
Solon 热加载与插件热插拔:Debug 模式 × E-Spi × H-Spi 全解析
java·solon·插件·plugin·热插拨
Rick19933 小时前
mysql联合索引经典实例
java·数据库·mysql
方也_arkling3 小时前
【Java-Day02】语法篇:变量/数据类型/标识符/运算符/类型转换
java·开发语言