1. 什么是 SpEL?
SpEL(Spring Expression Language)是 Spring 框架提供的一个强大的表达式语言,用于在运行时查询和操作对象图。它类似于 JSP EL(JavaServer Pages Expression Language)和 OGNL(Object-Graph Navigation Language),但在功能上更为丰富和强大。
官网文档:6. Spring Expression Language (SpEL)
中文文档:8. Spring 表达式语言 (SpEL)
2. SpEL 的主要特点
- 灵活性:可以在配置文件和代码中使用。
- 丰富的语法:支持字面量、变量引用、属性访问、方法调用、算术运算、逻辑运算、三元运算、集合操作、正则表达式等。
- 类型转换:自动进行类型转换。
- 对象图导航:可以方便地导航复杂的对象图。
- 环境变量访问:可以访问系统属性和环境变量。
3. SpEL 的基本语法
类别 | 描述 | 语法示例 | 备注 |
字面量 | 常量值,如字符串、数字、布尔值、空值等 | 'Hello, World!' | 字符串需要用单引号或双引号包围 |
123 | 数字可以直接写 | ||
true | 布尔值直接写 | ||
null | 空值直接写 | ||
变量引用 | 引用环境中的变量 | #{systemProperties['os.name']} | 使用 #{...} 语法引用变量 |
属性访问 | 访问对象的属性 | #{customer.name} | 使用点号 . 访问对象属性 |
方法调用 | 调用对象的方法 | #{customer.getName()} | 使用点号 . 调用对象方法 |
算术运算 | 基本的算术运算 | #{1 + 2} | 支持 +, -, *, /, %, ^ 运算符 |
逻辑运算 | 逻辑运算符 | #{1 == 2} | 支持 ==, !=, <, >, <=, >=, and, or, not 运算符 |
三元运算 | 三元运算符 | #{1 > 2 ? 'yes' : 'no'} | 格式为 condition ? true-expression : false-expression |
集合操作 | 操作集合和数组 | #{T(java.util.Arrays).asList('a', 'b', 'c').contains('b')} | 使用 T(...) 引用静态方法 |
正则表达式 | 正则表达式匹配 | #{'Hello'.matches('H.*')} | 使用 matches 方法进行匹配 |
类型转换 | 自动类型转换 | #{'30'} | 自动将字符串转换为数字 |
对象图导航 | 导航对象图 | #{customer.address.city} | 使用点号 . 导航对象图 |
环境变量 | 访问系统环境变量和属性 | #{systemProperties['user.name']} | 访问系统属性 |
#{systemEnvironment['AGE']} | 访问环境变量 |
4. 常用Spring的表达接口
4.1 class SpelExpressionParser
所属包:org.springframework.expression.spel.standard
父接口:org.springframework.expression.ExpressionParser
构造方法:
方法名 | 描述 |
SpelExpressionParser() | 创建一个新的 SpelExpressionParser 实例。 |
示例代码:
SpelExpressionParser parser = new SpelExpressionParser();
方法:
方法名 | 返回值类型 | 描述 |
parseExpression(String expressionString) | Expression | 解析给定的表达式字符串并返回 Expression 对象。 |
示例代码:
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("'Hello, World!'");
4.2 interface Expression
所属包:org.springframework.expression
方法:
方法名 | 返回值类型 | 描述 |
getValue() | Object | 计算表达式的值并返回结果。 |
getValue(EvaluationContext context) | Object | 在给定的上下文中计算表达式的值并返回结果。 |
getValue(EvaluationContext context, Class<T> desiredResultType) | T | 在给定的上下文中计算表达式的值,并将其转换为指定的类型。 |
示例代码:
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("'Hello, World!'");
StandardEvaluationContext context = new StandardEvaluationContext();
// getValue()
Object value1 = expression.getValue();
// getValue(EvaluationContext context)
Object value2 = expression.getValue(context);
// getValue(EvaluationContext context, Class<T> desiredResultType)
Object value3 = expression.getValue(context, String.class);
4.2 class StandardEvaluationContext
所属包:org.springframework.expression.spel.support
父接口:org.springframework.expression.EvaluationContext
构造方法:
方法名 | 描述 |
StandardEvaluationContext() | 创建一个新的 StandardEvaluationContext 实例。 |
StandardEvaluationContext(Object rootObject) | 使用给定的根对象创建一个新的 StandardEvaluationContext 实例。 |
示例代码:
StandardEvaluationContext context1 = new StandardEvaluationContext();
MyBean myBean = new MyBean();
StandardEvaluationContext context2 = new StandardEvaluationContext(myBean);public class MyBean {private int number = 10;public int getNumber() {return number;}
}
方法:
方法名 | 返回值类型 | 描述 |
setVariable(String name, Object value) | void | 在上下文中设置一个变量及其值。 |
示例代码:
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("number", 10);
4.3 SpEL 的基本语法示例代码
public class Address {private String city;public Address(String city) {this.city = city;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}
}
public class Customer {private String name;private int age;private Address address;private Customer() {}public Customer(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}
}
public static void main(String[] args) {// 1.字面量ExpressionParser parser = new SpelExpressionParser();String result = parser.parseExpression("'Hello, World!'").getValue(String.class);System.out.println(result); // 输出: Hello, World!// 2.变量引用StandardEvaluationContext context = new StandardEvaluationContext();context.setVariable("osName", System.getProperty("os.name"));String osName = parser.parseExpression("'OS Name: ' + #osName").getValue(context, String.class);System.out.println(osName); // 输出: OS Name: Windows 11// 3.属性访问Customer customer = new Customer("John Doe", 30);context.setRootObject(customer);String name = parser.parseExpression("name").getValue(context, String.class);System.out.println(name); // 输出: John Doe// 4.方法调用String upperCaseName = parser.parseExpression("name.toUpperCase()").getValue(context, String.class);System.out.println(upperCaseName); // 输出: JOHN DOE// 5.算术运算int agePlusTen = parser.parseExpression("age + 10").getValue(context, Integer.class);System.out.println(agePlusTen); // 输出: 40// 6.逻辑运算boolean isAdult = parser.parseExpression("age >= 18").getValue(context, Boolean.class);System.out.println(isAdult); // 输出: true// 7.三元运算String ageGroup = parser.parseExpression("age >= 18 ? 'Adult' : 'Minor'").getValue(context, String.class);System.out.println(ageGroup); // 输出: Adult// 8.集合操作Map<String, Map<String, String>> nestedMap = parser.parseExpression("{'person':{'name':'John','age':'30'}}").getValue(context, Map.class);System.out.println(nestedMap); // 输出: {person={name=John, age=30}}// 9.正则表达式匹配boolean matches = parser.parseExpression("'Hello'.matches('H.*')").getValue(Boolean.class);System.out.println(matches); // 输出: true// 10.类型转换int age = parser.parseExpression("'30'").getValue(Integer.class);System.out.println(age); // 输出: 30// 11.对象图导航Address address = new Address("New York");customer.setAddress(address);context.setRootObject(customer);String city = parser.parseExpression("address.city").getValue(context, String.class);System.out.println(city); // 输出: New York// 12.环境变量访问context.setVariable("systemProperties", System.getProperties());String javaHome = parser.parseExpression("#systemProperties['java.home']").getValue(context, String.class);System.out.println(javaHome);
}
5. SpEL 常见使用位置和场景
测试案例:GitHub - kerrsixy/spel-demo: SpEL表达式测试代码
1. 配置文件中
在 Spring 的 XML 配置文件中,你可以使用 SpEL 来动态设置 bean 属性。
<bean id="person" class="com.example.Person"><property name="name" value="#{'John Doe'}"/><property name="age" value="#{25 + 5}"/>
</bean>
2. 注解中
在 Spring 的注解中,你也可以使用 SpEL 来动态设置属性值。
@Data
@Component
public class Person {@Value("#{'John Doe'}")private String name;@Value("#{25 + 5}")private int age;
}
3. 方法参数中
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class DemoController {@GetMapping("/greet")public String greet(@Value("#{'Hello, ' + T(java.lang.System).currentTimeMillis()}") String message) {return message;}
}
4. AOP 切面中
在 Spring AOP 切面中,你可以使用 SpEL 来动态决定切点和通知的执行。
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Slf4j
@Aspect
@Component
public class LoggingAspect {@Before("execution(* com.zjp.speldemo.service.*.*(..)) && args(name) && @annotation(com.zjp.speldemo.annotation.Loggable)")public void logMethodEntry(@Value("#{name}") String name) {log.info("Entering method with name: {}", name);}
}
5. 与其他Spring组件
SpEL还可以在其他Spring组件中使用,比如在@Cacheable注解中定义缓存键:
import com.zjp.speldemo.entity.User;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class DemoController {@GetMapping@Cacheable(cacheNames = "userCache", key = "#id")public User getById(String id) {User user = new User(id, "zhangsan", 18);return user;}
}