Spring表达式语言(SpELl)
# 一、Spring表达式语言(SpEL)概述
# 1. SpEL的定义与作用
Spring表达式语言(SpEL,Spring Expression Language)是Spring框架提供的一种强大的表达式语言,用于在运行时查询和操作对象图。它支持在XML配置文件、注解以及编程式配置中使用,能够动态地计算表达式并返回结果。
SpEL的主要作用包括:
- 动态配置:在Spring配置文件中,SpEL可以用于动态地设置Bean的属性值。
- 条件判断:在注解或配置中,SpEL可以用于条件化地选择Bean或执行逻辑。
- 数据操作:SpEL支持对集合、数组、Map等数据结构进行操作,简化了复杂的数据处理逻辑。
# 2. SpEL的核心特性
SpEL具有以下核心特性,使其成为Spring框架中不可或缺的一部分:
- 强大的表达式语法:SpEL支持丰富的表达式语法,包括字面量、运算符、方法调用、属性访问等。
- 类型安全:SpEL支持类型转换和类型推断,确保表达式的结果与预期类型一致。
- 上下文感知:SpEL能够访问Spring应用上下文中的Bean、变量和属性,支持动态解析。
- 扩展性:SpEL允许开发者自定义函数、解析器和类型转换器,以满足特定需求。
- 性能优化:SpEL内置了缓存机制,能够提高表达式的解析和执行效率。
# 3. SpEL的应用场景
SpEL在Spring框架中有广泛的应用场景,主要包括:
Bean属性注入:在Spring配置文件中,使用SpEL动态设置Bean的属性值。例如:
<bean id="myBean" class="com.example.MyBean"> <property name="value" value="#{systemProperties['user.name']}" /> </bean>
这里,SpEL表达式
#{systemProperties['user.name']}
用于获取系统属性user.name
的值。条件化Bean注册:在注解中使用SpEL实现条件化Bean注册。例如:
@ConditionalOnExpression("#{systemProperties['env'] == 'prod'}") public class ProdConfig { // 生产环境配置 }
这里,SpEL表达式
#{systemProperties['env'] == 'prod'}
用于判断当前环境是否为生产环境。Spring Security中的权限控制:在Spring Security中,SpEL可以用于定义复杂的权限表达式。例如:
@PreAuthorize("hasRole('ADMIN') and #user.id == authentication.principal.id") public void updateUser(User user) { // 更新用户信息 }
这里,SpEL表达式
hasRole('ADMIN') and #user.id == authentication.principal.id
用于检查当前用户是否具有管理员权限,并且只能更新自己的信息。Spring MVC中的视图渲染:在Spring MVC中,SpEL可以用于动态渲染视图。例如:
<p>Welcome, ${#authentication.name}!</p>
这里,SpEL表达式
#authentication.name
用于获取当前认证用户的名称。
通过这些应用场景可以看出,SpEL在Spring框架中扮演着重要的角色,能够显著提升开发效率和代码的可读性。
# 二、SpEL的评估(Evaluation)
# 1. 表达式评估的基本概念
Spring表达式语言(SpEL)的评估是指解析和执行表达式的过程。SpEL表达式可以是一个简单的字符串,也可以是一个复杂的逻辑表达式。评估的目的是从表达式中提取出有意义的值或执行特定的操作。
# 1.1 表达式的组成
SpEL表达式通常由以下部分组成:
- 字面量:如字符串、数字、布尔值等。
- 变量:可以在表达式中引用的变量。
- 运算符:如算术运算符、逻辑运算符等。
- 方法调用:调用对象的方法。
- 属性访问:访问对象的属性。
# 1.2 评估的过程
SpEL的评估过程包括以下步骤:
- 解析表达式:将表达式字符串解析为抽象语法树(AST)。
- 上下文绑定:将表达式与评估上下文绑定,确定变量和属性的来源。
- 执行表达式:根据解析后的语法树执行表达式,生成结果。
# 1.3 示例代码
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class SpELExample {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("'Hello, World!'");
String result = (String) expression.getValue();
System.out.println(result); // 输出: Hello, World!
}
}
# 2. 评估上下文
评估上下文(Evaluation Context)是SpEL评估过程中用于提供变量、函数和类型信息的上下文环境。它决定了表达式中的变量和属性如何解析和执行。
# 2.1 评估上下文的类型
- StandardEvaluationContext:默认的评估上下文,支持完整的SpEL功能。
- SimpleEvaluationContext:轻量级的评估上下文,适用于简单的表达式评估,安全性更高。
StandardEvaluationContext
支持完整的 SpEL 功能,包括方法调用、类型转换、复杂表达式等。
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 StandardEvaluationContextExample {
public static void main(String[] args) {
// 创建一个 SpEL 解析器
ExpressionParser parser = new SpelExpressionParser();
// 创建一个对象
Person person = new Person("John", 30);
// 创建 StandardEvaluationContext,并设置根对象
StandardEvaluationContext context = new StandardEvaluationContext(person);
// 解析并执行表达式
Expression expression = parser.parseExpression("name.toUpperCase()");
String result = expression.getValue(context, String.class);
System.out.println("Result: " + result); // 输出: Result: JOHN
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
说明:
- 这里使用了
StandardEvaluationContext
,支持调用toUpperCase()
方法。 StandardEvaluationContext
可以处理复杂的表达式,例如方法调用、属性访问等。
SimpleEvaluationContext
只支持简单的表达式,例如属性访问和简单的条件判断。
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
public class SimpleEvaluationContextExample {
public static void main(String[] args) {
// 创建一个 SpEL 解析器
ExpressionParser parser = new SpelExpressionParser();
// 创建一个对象
Person person = new Person("Alice", 25);
// 创建 SimpleEvaluationContext,并设置根对象
SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// 解析并执行表达式
Expression expression = parser.parseExpression("name");
String result = expression.getValue(context, person, String.class);
System.out.println("Result: " + result); // 输出: Result: Alice
}
}
说明:
- 这里使用了
SimpleEvaluationContext
,只支持简单的属性访问(如name
)。 - 如果尝试调用方法(如
toUpperCase()
),会抛出异常,因为SimpleEvaluationContext
不支持方法调用。
# 3. 评估结果的处理
评估结果的处理是指对SpEL表达式评估后生成的结果进行进一步的操作或转换。
# 3.1 结果类型
SpEL表达式的结果可以是以下类型:
- 基本类型:如字符串、数字、布尔值等。
- 对象:如Java对象、集合等。
- 空值:如果表达式无法解析或执行,可能返回
null
。
# 3.2 结果转换
可以通过getValue
方法的重载版本将结果转换为特定的类型。
# 3.3 示例代码
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class SpELResultExample {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("10 + 20");
Integer result = expression.getValue(Integer.class);
System.out.println(result); // 输出: 30
}
}
# 4. 解析器配置
SpelParserConfiguration
是用于配置 SpEL 解析器的类,它允许开发者自定义解析器的行为,例如启用自动类型转换、设置空引用处理方式以及配置表达式编译模式。
# 4.1 主要配置选项
- 自动类型转换:是否允许在表达式中自动进行类型转换。
- 空引用处理:当表达式中的变量或属性为
null
时,是否抛出异常。 - 表达式编译模式:是否启用表达式编译以提高性能。
# 4.2 示例代码
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class SpELParserConfigExample {
public static void main(String[] args) {
// 配置解析器:启用自动类型转换,允许空引用,启用表达式编译
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
ExpressionParser parser = new SpelExpressionParser(config);
// 解析并执行表达式
Expression expression = parser.parseExpression("10 + 20");
Integer result = expression.getValue(Integer.class);
System.out.println(result); // 输出: 30
}
}
# 4.3 配置选项详解
- 自动类型转换:如果设置为
true
,SpEL 会尝试将表达式中不匹配的类型自动转换为目标类型。例如,将字符串"10"
转换为整数10
。 - 空引用处理:如果设置为
true
,当表达式中的变量或属性为null
时,SpEL 会返回null
而不是抛出异常。 - 表达式编译模式:可以通过
SpelCompilerMode
配置表达式是否编译为字节码以提高性能。
# 5. SpEL 编译
SpEL 支持将表达式编译为字节码,以提高表达式的执行性能。编译后的表达式可以直接执行,而无需每次重新解析。
# 5.1 编译器配置
SpEL 编译器可以通过 SpelCompilerMode
进行配置,支持以下模式:
- OFF:禁用编译。
- IMMEDIATE:立即编译表达式。
- MIXED:混合模式,根据表达式的复杂性决定是否编译。
# 5.2 启用编译
可以通过设置 SpelParserConfiguration
来启用编译功能。
# 5.3 示例代码
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelCompilerMode;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class SpELCompilationExample {
public static void main(String[] args) {
// 配置解析器:启用 IMMEDIATE 编译模式
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null);
ExpressionParser parser = new SpelExpressionParser(config);
// 解析并执行表达式
Expression expression = parser.parseExpression("10 + 20");
Integer result = expression.getValue(Integer.class);
System.out.println(result); // 输出: 30
}
}
# 5.4 编译器的限制
尽管 SpEL 编译可以提高性能,但它有一些限制:
- 不支持所有表达式:某些复杂的表达式可能无法编译。
- 动态性受限:编译后的表达式无法动态修改。
- 调试困难:编译后的字节码难以调试。
# 三、在Bean定义中使用SpEL
Spring表达式语言(SpEL)不仅可以用于动态计算值,还可以在Spring的Bean定义中使用。通过SpEL,可以在XML配置、注解配置和Java配置中动态地注入属性值或执行逻辑。以下是具体的使用方式。
# 1. 在XML配置中使用SpEL
在XML配置文件中,可以使用#{expression}
语法来嵌入SpEL表达式。这种方式非常适合在传统的Spring XML配置中使用。
# 1.1 基本用法
在XML中,可以通过<property>
标签的value
属性或<constructor-arg>
标签的value
属性来使用SpEL表达式。
# 1.2 示例代码
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义一个Bean -->
<bean id="myBean" class="com.example.MyBean">
<!-- 使用SpEL表达式注入属性 -->
<property name="message" value="#{'Hello, ' + systemProperties['user.name']}" />
</bean>
</beans>
解释:
systemProperties['user.name']
:获取系统属性中的用户名。#{'Hello, ' + ...}
:将字符串与系统属性拼接。
# 2. 在注解配置中使用SpEL
在基于注解的配置中,可以使用@Value
注解来注入SpEL表达式的值。这种方式非常适合在Spring Boot或现代Spring应用中使用。
# 2.1 基本用法
@Value
注解可以直接用于字段、方法参数或构造函数参数上,支持SpEL表达式。
# 2.2 示例代码
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
@Value("#{systemProperties['user.name']}")
private String userName;
@Value("#{T(java.lang.Math).random() * 100}")
private double randomNumber;
public void printInfo() {
System.out.println("User Name: " + userName);
System.out.println("Random Number: " + randomNumber);
}
}
解释:
@Value("#{systemProperties['user.name']}")
:注入系统属性中的用户名。@Value("#{T(java.lang.Math).random() * 100}")
:注入一个0到100之间的随机数。
# 3. 在Java配置中使用SpEL
在基于Java的配置中,可以通过@Value
注解或Environment
对象来使用SpEL表达式。这种方式非常适合在Spring的Java配置类中使用。
# 3.1 基本用法
在Java配置类中,可以直接在字段或方法参数上使用@Value
注解,或者通过Environment
对象获取SpEL表达式的值。
# 3.2 示例代码
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Value("#{systemProperties['java.version']}")
private String javaVersion;
@Bean
public MyBean myBean() {
MyBean bean = new MyBean();
bean.setJavaVersion(javaVersion);
return bean;
}
}
解释:
@Value("#{systemProperties['java.version']}")
:注入Java版本信息。- 在
@Bean
方法中,将SpEL表达式的值注入到Bean中。
# 四、SpEL语言参考
# A. 字面量表达式
# 1. 字符串字面量
字符串字面量用单引号或双引号表示。
Expression expression = parser.parseExpression("'Hello, World!'");
String result = (String) expression.getValue();
# 2. 数字字面量
数字字面量可以是整数、浮点数等。
Expression expression = parser.parseExpression("42");
Integer result = (Integer) expression.getValue();
# 3. 布尔字面量
布尔字面量包括true
和false
。
Expression expression = parser.parseExpression("true");
Boolean result = (Boolean) expression.getValue();
# 4. null字面量
null
表示空值。
Expression expression = parser.parseExpression("null");
Object result = expression.getValue();
# B. 属性、数组、列表、映射和索引器
# 1. 访问对象属性
使用点号(.
)访问对象属性。
Expression expression = parser.parseExpression("person.name");
String name = (String) expression.getValue(context);
# 2. 访问数组和列表元素
使用方括号([]
)访问数组或列表元素。
Expression expression = parser.parseExpression("list[0]");
Object element = expression.getValue(context);
# 3. 访问映射元素
使用方括号([]
)访问映射元素。
Expression expression = parser.parseExpression("map['key']");
Object value = expression.getValue(context);
# 4. 使用索引器
索引器可以用于访问集合或数组中的元素。
Expression expression = parser.parseExpression("array[0]");
Object element = expression.getValue(context);
# C. 内联列表
使用花括号({}
)定义内联列表。
Expression expression = parser.parseExpression("{1, 2, 3}");
List<Integer> list = (List<Integer>) expression.getValue();
# D. 内联映射
使用花括号({}
)和冒号(:
)定义内联映射。
Expression expression = parser.parseExpression("{'key1': 'value1', 'key2': 'value2'}");
Map<String, String> map = (Map<String, String>) expression.getValue();
# E. 数组构造
使用new
关键字构造数组。
Expression expression = parser.parseExpression("new int[]{1, 2, 3}");
int[] array = (int[]) expression.getValue();
# F. 方法
# 1. 调用方法
使用点号(.
)调用方法。
Expression expression = parser.parseExpression("'Hello, World!'.toUpperCase()");
String result = (String) expression.getValue();
# 2. 方法引用
可以直接引用方法。
class Society {
public boolean isMember(String name) {
// 检查是否是会员的逻辑
return true; // 示例返回值
}
}
Society society = new Society();
StandardEvaluationContext societyContext = new StandardEvaluationContext(society);
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean.class);
# G. 运算符
# 1. 算术运算符
支持加(+
)、减(-
)、乘(*
)、除(/
)等。
Expression expression = parser.parseExpression("10 + 20");
Integer result = (Integer) expression.getValue();
# 2. 关系运算符
支持等于(==
)、不等于(!=
)、大于(>
)、小于(<
)等。
Expression expression = parser.parseExpression("10 > 20");
Boolean result = (Boolean) expression.getValue();
# 3. 逻辑运算符
支持与(&&
)、或(||
)、非(!
)等。
Expression expression = parser.parseExpression("true && false");
Boolean result = (Boolean) expression.getValue();
# 4. 条件运算符
支持三元运算符(? :
)。
Expression expression = parser.parseExpression("10 > 20 ? 'Yes' : 'No'");
String result = (String) expression.getValue();
# H. 类型
# 1. 类型转换
使用T()
进行类型转换。
Expression expression = parser.parseExpression("T(java.lang.Integer).parseInt('42')");
Integer result = (Integer) expression.getValue();
# 2. 类型判断
使用instanceof
进行类型判断。
Expression expression = parser.parseExpression("'Hello' instanceof T(String)");
Boolean result = (Boolean) expression.getValue();
# I. 构造函数
使用new
关键字调用构造函数。
Expression expression = parser.parseExpression("new java.util.Date()");
Date date = (Date) expression.getValue();
# J. 变量
# 1. 定义变量
在上下文中定义变量。
context.setVariable("name", "Spring");
# 2. 使用变量
使用#
符号引用变量。
Expression expression = parser.parseExpression("#name");
String result = (String) expression.getValue(context);
# K. 函数
# 1. 定义函数
在上下文中注册函数。
context.setVariable("reverse", new ReverseFunction());
# 2. 调用函数
使用#
符号调用函数。
Expression expression = parser.parseExpression("#reverse('Hello')");
String result = (String) expression.getValue(context);
# M. Bean引用
# 1. 引用Bean
使用@
符号引用Bean。
AnnotationConfigApplicationContext context = ...;
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(context));
Expression expression = parser.parseExpression("@someBean");
MyBean bean = (MyBean) expression.getValue(context);
Expression expression = parser.parseExpression("@'order.service'");
OrderService bean = (OrderService) expression.getValue(context);
# 2. 访问FactoryBean本身
使用&
访问FactoryBean本身。
Object factoryBean = parser.parseExpression("&someFactoryBean").getValue(context);
# N. 三元运算符(If-Then-Else)
# 1. 三元运算符的使用
Expression expression = parser.parseExpression("10 > 20 ? 'Yes' : 'No'");
String result = (String) expression.getValue();
# 2. 三元运算符的嵌套
Expression expression = parser.parseExpression("10 > 20 ? 'Yes' : (5 > 10 ? 'Maybe' : 'No')");
String result = (String) expression.getValue();
# O. Elvis运算符
# 1. Elvis运算符的使用
用于简化空值检查。
Expression expression = parser.parseExpression("name ?: 'Unknown'");
String result = (String) expression.getValue(context);
# 2. Elvis运算符的替代方案
可以使用三元运算符替代。
Expression expression = parser.parseExpression("name != null ? name : 'Unknown'");
String result = (String) expression.getValue(context);
# P. 安全导航运算符
# 1. 安全导航运算符的使用
避免空指针异常。
Expression expression = parser.parseExpression("person?.name");
String name = (String) expression.getValue(context);
# 2. 安全导航运算符的优势
简化空值检查逻辑。
Expression expression = parser.parseExpression("person?.address?.city");
String city = (String) expression.getValue(context);
# Q. 集合选择
# 1. 集合选择的基本概念
从集合中选择符合条件的元素。
Expression expression = parser.parseExpression("list.?[#this > 10]");
List<Integer> filteredList = (List<Integer>) expression.getValue(context);
# 2. 使用集合选择
Expression expression = parser.parseExpression("list.?[#this > 10].size()");
Integer size = (Integer) expression.getValue(context);
# R. 集合投影
对集合中的每个元素进行转换。
Expression expression = parser.parseExpression("list.![#this * 2]");
List<Integer> transformedList = (List<Integer>) expression.getValue(context);
每个元素都乘以二输出。
# S. 表达式模板
将静态文本与动态表达式结合。
Expression expression = parser.parseExpression("Hello, #{name}!", new TemplateParserContext());
String result = (String) expression.getValue(context);
TemplateParserContext是 Spring 提供的一个
ParserContext` 实现,用于解析模板字符串中的占位符。它的定义如下:
public class TemplateParserContext implements ParserContext {
@Override
public String getExpressionPrefix() {
return "#{";
}
@Override
public String getExpressionSuffix() {
return "}";
}
@Override
public boolean isTemplate() {
return true;
}
}
getExpressionPrefix
:返回占位符的前缀,默认是#{
。getExpressionSuffix
:返回占位符的后缀,默认是}
。isTemplate
:返回true
,表示这是一个模板字符串。
通过 TemplateParserContext
,SpEL 会将 #{name}
识别为一个表达式占位符,而不是普通文本。
# 五、总结
SpEL作为Spring框架的重要组成部分,为开发者提供了强大的动态计算能力。通过掌握SpEL的语法和特性,开发者可以更高效地构建灵活、可扩展的Spring应用。希望本文的内容能够帮助您更好地理解和使用SpEL,在实际项目中发挥其最大价值。
祝你变得更强!