Spring Expression Language (SpEL) 这个概念是在阅读博文中偶然发现的,自信也玩了SpringBoot好几年,竟然第一次听说这个东西。。。。。。SpEL是一个功能强大的表达式语言,它用于在 Spring 框架中查询和操作对象。SpEL 是 Spring 框架的一部分,它使得开发者能够以声明式方式处理复杂的逻辑,而无需编写大量的 Java 代码。
SpEL 的基本功能
-
简单计算
- 可以执行基本的算术运算。
javaExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("10 * 2"); System.out.println(exp.getValue()); // 输出 20 -
访问对象属性
- 可以通过表达式直接访问对象的属性或方法。
javaclass Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } Person person = new Person(); person.setName("John"); ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("name"); System.out.println(exp.getValue(person)); // 输出 John -
条件运算和逻辑判断
- 支持条件表达式、逻辑运算符(AND、OR、NOT)等。
javaExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("true and false"); System.out.println(exp.getValue()); // 输出 false -
集合操作
- SpEL 可以方便地对集合类型进行操作(如 List、Map、Set 等)。
javaList<Integer> list = Arrays.asList(1, 2, 3, 4, 5); ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("#list[0]"); System.out.println(exp.getValue(Collections.singletonMap("list", list))); // 输出 1 -
内置函数
- SpEL 提供了丰富的内置函数,可以用来进行各种常见操作。
javaExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression("'Hello'.toUpperCase()"); System.out.println(exp.getValue()); // 输出 "HELLO" -
引用 Spring Bean
- 在 Spring 上下文中,可以通过 SpEL 引用和操作 Spring Bean。
xml<bean id="person" class="com.example.Person"> <property name="name" value="John"/> </bean> <bean id="testBean" class="com.example.TestBean"> <property name="person" value="#person"/> </bean>在 Spring 配置中,你可以通过 SpEL 来引用这些 Bean。
-
动态属性注入
- 通过 SpEL,可以动态地设置或获取对象的属性值,甚至支持通过表达式对字段进行操作。
SpEL 的常见应用场景
-
Spring Bean 配置
- 在 Spring 的配置文件中,可以使用 SpEL 来动态地设置 Bean 的属性值或条件化 Bean 的创建。
-
条件化注入
- 使用 SpEL 来决定是否注入某个 Bean。例如,基于某些条件才注入 Bean。
-
动态查询
- 在使用 Spring Data 或 Hibernate 时,SpEL 可以帮助动态地构造查询条件。
-
配置文件中的属性解析
- 在 Spring 配置文件中,SpEL 可以用于解析字符串、文件路径、日期等复杂类型的数据。
使用SpEL动态配置bean
1. 使用XML配置Spring Bean
在Spring的XML配置文件中,可以通过<property>标签来使用SpEL来动态地配置Bean的属性值。
示例:
xml
<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="employee" class="com.example.Employee">
<property name="name" value="John Doe" />
<property name="salary" value="#{1000 * 1.2}" /> <!-- 使用SpEL -->
</bean>
<!-- 配置一个Bean使用SpEL计算表达式 -->
<bean id="company" class="com.example.Company">
<property name="employee" ref="employee" />
<property name="annualBonus" value="#{T(java.lang.Math).PI * 100}" /> <!-- 使用Math类 -->
</bean>
</beans>
在上面的配置中:
#{1000 * 1.2}:SpEL表达式,表示将1000乘以1.2来计算工资。#{T(java.lang.Math).PI * 100}:通过T()操作符引用Java的Math类,获取PI值并计算年度奖金。
2. 使用注解配置Spring Bean
Spring支持在注解中使用SpEL,通常是通过@Value注解来注入SpEL表达式。
示例:
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Employee {
@Value("#{1000 * 1.5}")
private double salary; // SpEL注入动态计算值
@Value("#{T(java.lang.Math).PI * 100}")
private double annualBonus; // 使用PI计算奖金
// Getter and Setter
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getAnnualBonus() {
return annualBonus;
}
public void setAnnualBonus(double annualBonus) {
this.annualBonus = annualBonus;
}
}
3. 使用Spring Boot自动配置
如果使用Spring Boot,可以利用@Value注解和SpEL表达式在应用程序的配置中动态注入值。通常,这些值来自于application.properties或application.yml文件。
示例:
properties
# application.properties
salary=1000
multiplier=1.5
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Employee {
@Value("#{${salary} * ${multiplier}}")
private double salary;
// Getter and Setter
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
4. SpEL常见操作符
- 数学运算 :
+,-,*,/,%等。 - 逻辑运算 :
and,or,not。 - 集合操作 :
.size(),.isEmpty(),.contains(),.toArray()等。 - 引用类型 :
T(className):引用Java类型。 - 条件判断 :
?::类似于三元运算符。
示例:
xml
<bean id="person" class="com.example.Person">
<property name="age" value="#{T(java.lang.Integer).parseInt('25')}" />
<property name="isAdult" value="#{person.age >= 18}" />
</bean>
这里,#{T(java.lang.Integer).parseInt('25')}是将字符串 '25' 转换为整数,#{person.age >= 18} 判断是否成年。
使用SpEL进行条件化注入
1.条件注入实例
SpEL可以在Spring配置中根据一定的条件动态地选择值或Bean,最常见的方式是使用@Value注解或者通过XML配置的<property>来完成条件化注入。
示例 1:基于属性文件的条件注入
假设我们根据application.properties中的配置值决定注入不同的Bean或值。
步骤:
- 在
application.properties中配置条件值:
properties
env=production
- 使用
@Value注解配合SpEL表达式进行条件化注入:
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MyService {
@Value("#{ '${env}' == 'production' ? 'Production Bean' : 'Development Bean' }")
private String environmentBean;
public String getEnvironmentBean() {
return environmentBean;
}
}
在这个例子中,SpEL表达式判断env属性的值:
- 如果
env为production,则注入'Production Bean'。 - 否则,注入
'Development Bean'。
当application.properties中的env=production时,environmentBean将被注入'Production Bean'。
示例 2:基于条件选择Bean
假设你需要根据条件选择不同的Bean进行注入。
步骤:
- 创建不同的Bean:
java
@Component
public class ProductionService implements IService {
@Override
public void execute() {
System.out.println("Running in production environment.");
}
}
@Component
public class DevelopmentService implements IService {
@Override
public void execute() {
System.out.println("Running in development environment.");
}
}
- 使用SpEL选择性注入:
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MyApp {
@Value("#{${env} == 'production' ? productionService : developmentService}")
private IService service;
public void run() {
service.execute();
}
}
在上面的代码中:
@Value注解使用SpEL表达式来决定注入productionService或developmentService。- 如果
env为production,productionService会被注入到service中。 - 如果
env为development,developmentService会被注入。
2. 使用Spring的@Conditional注解和SpEL
Spring框架也提供了@Conditional注解,可以基于某些条件来控制Bean的加载。虽然@Conditional本身不是直接与SpEL相关的,但是你可以结合SpEL表达式来做更复杂的条件化控制。
示例:基于SpEL的自定义@Condition
步骤:
- 创建自定义条件类:
java
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class OnProductionCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String env = context.getEnvironment().getProperty("env");
return "production".equals(env);
}
}
- 使用
@Conditional注解来控制Bean的加载:
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
@Conditional(OnProductionCondition.class)
public MyService productionService() {
return new MyService("Production Service");
}
@Bean
@Conditional(OnDevelopmentCondition.class)
public MyService developmentService() {
return new MyService("Development Service");
}
}
在这个例子中:
OnProductionCondition类用于检查env属性是否为production,如果是,则加载productionServiceBean。- 你可以根据条件加载不同的服务Bean。
3. 使用@Profile与SpEL结合
Spring的@Profile注解用于根据不同的环境加载不同的Bean。如果你结合@Profile和SpEL表达式,就可以根据环境变量或配置值进行更灵活的条件化注入。
示例:结合@Profile与SpEL
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class AppConfig {
@Bean
@Profile("#{systemProperties['env'] == 'production' ? 'production' : 'development'}")
public MyService myService() {
return new MyService("Conditional Service");
}
}
在这个例子中,@Profile与SpEL结合,判断系统属性env的值来决定加载哪个Profile的Bean。
SpEL(Spring Expression Language)不仅支持基本的表达式计算,还能够执行动态查询。这在Spring Data或Hibernate等框架中非常有用,尤其是在构建动态查询时。通过SpEL,开发者可以根据一定的条件动态生成查询语句,而不需要手动拼接字符串,避免了SQL注入风险。
1使用SpEL执行动态查询(Spring Data JPA)
1.执行SDJ
示例 1:动态查询条件
假设我们有一个Person实体,包含name和age属性,且我们希望根据传入的条件来动态地构建查询。
实体类:
java
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class Person {
@Id
private Long id;
private String name;
private int age;
// Getter and Setter methods
}
动态查询接口:
java
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.JpaRepository;
@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
@Query("SELECT p FROM Person p WHERE " +
"(:#{#name} IS NULL OR p.name = :#{#name}) AND " +
"(:#{#age} IS NULL OR p.age = :#{#age})")
List<Person> findByNameAndAge(@Param("name") String name, @Param("age") Integer age);
}
在这个示例中,SpEL表达式动态地构建查询条件:
(:#{#name} IS NULL OR p.name = :#{#name}):如果name为null,则该条件会被忽略(不对name进行过滤),否则会按name进行过滤。(:#{#age} IS NULL OR p.age = :#{#age}):同样的逻辑适用于age,当age为null时不进行过滤。
2. 使用SpEL和Spring Data MongoDB进行动态查询
SpEL也可以和Spring Data MongoDB结合使用,执行动态查询。
示例 2:MongoDB的动态查询
假设我们有一个Product实体,包含name和price,并希望根据传入的条件动态构建查询。
实体类:
java
import org.springframework.data.annotation.Id;
public class Product {
@Id
private String id;
private String name;
private double price;
// Getter and Setter methods
}
动态查询接口:
java
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
public interface ProductRepository extends MongoRepository<Product, String> {
@Query("{ 'name' : ?0, 'price' : ?1 }")
List<Product> findByNameAndPrice(String name, double price);
@Query("{ 'name' : ?0 }")
List<Product> findByName(String name);
}
你可以在查询方法中结合SpEL来执行动态查询,例如:
java
@Query("{ 'name' : :#{#name != null ? '#name' : 'defaultName'} }")
List<Product> findByDynamicName(@Param("name") String name);
在这个查询中,SpEL允许你根据name参数的值来动态决定查询条件。如果name为空,查询将使用defaultName。
3. 使用SpEL执行动态查询条件(Spring Data JDBC)
在Spring Data JDBC中,SpEL的应用也能动态构建查询条件。下面是一个使用SpEL的动态查询例子。
示例 3:Spring Data JDBC动态查询
实体类:
java
public class Employee {
private Long id;
private String name;
private int salary;
// Getter and Setter methods
}
动态查询接口:
java
import org.springframework.data.jdbc.core.query.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
@Query("SELECT * FROM Employee WHERE " +
"(:#{#name} IS NULL OR name = :#{#name}) AND " +
"(:#{#salary} IS NULL OR salary = :#{#salary})")
List<Employee> findByNameAndSalary(@Param("name") String name, @Param("salary") Integer salary);
}
在这个例子中,SpEL表达式用于根据条件动态生成SQL查询:
- 如果
name为null,则不对name进行筛选。 - 如果
salary为null,则不对salary进行筛选。
4. 使用SpEL与Spring Data Redis结合
Spring Data Redis可以与SpEL结合,进行动态查询和数据操作。假设我们需要根据不同的条件查询Redis中的数据,可以利用SpEL来灵活地控制查询条件。
示例 4:Spring Data Redis的动态查询
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class RedisService {
private final RedisTemplate<String, String> redisTemplate;
public RedisService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public String getValueByCondition(String key, String defaultValue) {
return redisTemplate.opsForValue().getOrDefault(
"#{T(org.springframework.util.StringUtils).hasText(#key) ? #key : #defaultValue}", defaultValue);
}
}
在这个例子中,SpEL的T(org.springframework.util.StringUtils).hasText(#key)被用来检查传入的key是否为空或空格,进而选择是否使用默认值。
SpEL(Spring Expression Language)不仅可以用于表达式计算,还可以非常方便地解析配置文件中的属性。通过使用SpEL,可以在Spring应用程序中动态地读取配置文件中的值,并基于这些值做出不同的决策或配置。
下面我将给出一些使用SpEL解析配置文件属性的实例。
解析配置文件属性
1. 使用SpEL解析application.properties文件中的属性
假设你有一个application.properties文件,其中包含一些配置信息:
application.properties:
properties
myapp.username=admin
myapp.password=secret
myapp.url=https://example.com
2. 使用@Value注解结合SpEL注入属性
可以使用@Value注解结合SpEL从配置文件中读取属性。使用${}符号来引用配置文件中的属性,然后通过SpEL解析。
示例:
Java类:
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class AppConfig {
@Value("#{systemProperties['user.name']}")
private String userName;
@Value("${myapp.username}")
private String username;
@Value("${myapp.password}")
private String password;
@Value("${myapp.url}")
private String url;
@Value("#{T(java.lang.Math).random() * 100}")
private double randomValue;
public void printConfig() {
System.out.println("System user name: " + userName);
System.out.println("App username: " + username);
System.out.println("App password: " + password);
System.out.println("App URL: " + url);
System.out.println("Random value: " + randomValue);
}
}
解释:
@Value("${myapp.username}"):从application.properties中读取myapp.username的值并注入到username字段中。@Value("#{systemProperties['user.name']}"):通过SpEL获取系统属性user.name,即当前操作系统的用户名。@Value("#{T(java.lang.Math).random() * 100}"):通过SpEL计算一个随机值并赋给randomValue。
3. 使用SpEL解析application.yml文件中的属性
Spring也支持从YAML格式的配置文件中读取属性。假设你的配置文件是application.yml,你也可以用SpEL来解析这些属性。
application.yml:
yaml
myapp:
username: admin
password: secret
url: https://example.com
Java类:
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class AppConfig {
@Value("${myapp.username}")
private String username;
@Value("${myapp.password}")
private String password;
@Value("${myapp.url}")
private String url;
@Value("#{myapp.username == 'admin' ? 'admin_role' : 'user_role'}")
private String role;
public void printConfig() {
System.out.println("Username: " + username);
System.out.println("Password: " + password);
System.out.println("URL: " + url);
System.out.println("Role: " + role);
}
}
解释:
@Value("${myapp.username}"):从application.yml文件中读取myapp.username的值并注入到username字段。@Value("#{myapp.username == 'admin' ? 'admin_role' : 'user_role'}"):使用SpEL表达式根据myapp.username的值动态决定role的值。
4. 使用SpEL动态注入复杂的配置值
假设你有更复杂的配置,例如某些属性值依赖于其他属性。SpEL可以非常方便地处理这些动态值。
application.properties:
properties
myapp.base-url=https://example.com/api
myapp.endpoint=/users
myapp.full-url=#{T(java.lang.String).format('%s%s', '${myapp.base-url}', '${myapp.endpoint}')}
Java类:
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class AppConfig {
@Value("${myapp.base-url}")
private String baseUrl;
@Value("${myapp.endpoint}")
private String endpoint;
@Value("${myapp.full-url}")
private String fullUrl;
public void printConfig() {
System.out.println("Base URL: " + baseUrl);
System.out.println("Endpoint: " + endpoint);
System.out.println("Full URL: " + fullUrl);
}
}
解释:
@Value("${myapp.base-url}"):从配置文件中读取base-url属性。@Value("${myapp.endpoint}"):从配置文件中读取endpoint属性。@Value("#{T(java.lang.String).format('%s%s', '${myapp.base-url}', '${myapp.endpoint}')}"):使用SpEL表达式拼接base-url和endpoint属性,构造出完整的URL。
5. 使用SpEL在条件表达式中解析配置
有时候,你可能需要在读取配置时做一些逻辑判断。例如,配置的某个属性值可能决定应用是否启用某个功能。SpEL允许你在注入属性时进行条件判断。
application.properties:
properties
myapp.featureEnabled=true
Java类:
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class AppConfig {
@Value("${myapp.featureEnabled}")
private boolean featureEnabled;
@Value("#{${myapp.featureEnabled} ? 'Feature is enabled' : 'Feature is disabled'}")
private String featureStatus;
public void printConfig() {
System.out.println("Feature Enabled: " + featureEnabled);
System.out.println("Feature Status: " + featureStatus);
}
}
解释:
@Value("${myapp.featureEnabled}"):从配置文件读取featureEnabled的值。@Value("#{${myapp.featureEnabled} ? 'Feature is enabled' : 'Feature is disabled'}"):使用SpEL判断featureEnabled的值,根据它的布尔值决定featureStatus的值。
写在最后
SpEL 了解了以后,才知道它是一个如此强大工具,熟练掌握它可以提高我们的代码开发效率,将诸多逻辑判断的代码浓缩成一行注解。