当前位置: 首页 > news >正文

Spring EL 表达式

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

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

基础使用

1、字符串处理

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、带上下文变量

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

@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、表达式运算

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、逻辑运算

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、访问集合元素

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()

	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()

protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,final ConfigurablePropertyResolver propertyResolver) throws BeansException {//这里的propertyResolver实例类型是PropertySourcesPropertyResolverpropertyResolver.setPlaceholderPrefix(this.placeholderPrefix);propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);propertyResolver.setValueSeparator(this.valueSeparator);//根据ignoreUnresolvablePlaceholders配置来生成ResolverStringValueResolver 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()

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {if (!shouldIgnoreSpel) {beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));}
}

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

private String expressionPrefix = "#{";
private String expressionSuffix = "}";

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

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

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

@Value("{{2 + 3}}")
private int value;  // 输出 5

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

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

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

http://www.dtcms.com/a/570004.html

相关文章:

  • 利用海伦公式计算经纬度坐标点到直线的距离
  • 前端面试题最大矩形面积问题
  • 网站建设顺德营销型网站制作msgg
  • 19、【Ubuntu】【远程开发】技术方案分析:远程桌面
  • 从零到一:我的开源AI商业化实战之路
  • 景县网站建设在线培训平台
  • 第21课:前端界面开发:用Gradio构建RAG应用UI
  • 网站原型图是什么做网站的数据从哪里来
  • 网站文件权限设置金融投资网站源码
  • 织梦手机网站怎么修改密码html网站设计模板下载
  • 哪种网站开发最简单家庭宽带做网站稳定吗
  • 浅谈Linux内核kswapd的内存域(zone)扫描机制
  • 什么是北斗短报文终端?与卫星电话有什么区别?
  • Maven基础(一)
  • MAC-SQL:黄金标准错误
  • 怎样创建基本的网站电子商务网站建设的试卷
  • 网站加盟代理wordpress 本地调试
  • 如何使用AI快速编程实现标注ROS2中sensor_msgs/msg/Image图像色彩webots2025a
  • 专业定制网站开发公司做纺织的用什么网站
  • 20251104让AIO-3576Q38开发板跑Rockchip的原厂Android14进行性能测试【使用天启的DTS】
  • 【案例】三维扫描实现
  • 无人设备遥控器之天线技术分析
  • 宁波市建设工程监理协会网站工程公司年会发言稿
  • 广州旅游团购网站建设中山seo
  • mlir 编译器学习笔记之四 -- 调度
  • 网站建设实施步骤网站源代码上传
  • 2025年 汽车零部件行业MES厂商分析
  • 学校网站制作公司公司企业邮箱怎么填写
  • 网站改版 目的网络建设公司前景
  • Milvus:索引概述(十二)