【SpringBoot从初学者到专家的成长25】认识SpringBoot中的Spring Expression Language (SpEL)

Spring Expression Language (SpEL) 这个概念是在阅读博文中偶然发现的,自信也玩了SpringBoot好几年,竟然第一次听说这个东西。。。。。。SpEL是一个功能强大的表达式语言,它用于在 Spring 框架中查询和操作对象。SpEL 是 Spring 框架的一部分,它使得开发者能够以声明式方式处理复杂的逻辑,而无需编写大量的 Java 代码。

SpEL 的基本功能

  1. 简单计算

    • 可以执行基本的算术运算。
    java 复制代码
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression("10 * 2");
    System.out.println(exp.getValue());  // 输出 20
  2. 访问对象属性

    • 可以通过表达式直接访问对象的属性或方法。
    java 复制代码
    class 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
  3. 条件运算和逻辑判断

    • 支持条件表达式、逻辑运算符(AND、OR、NOT)等。
    java 复制代码
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression("true and false");
    System.out.println(exp.getValue());  // 输出 false
  4. 集合操作

    • SpEL 可以方便地对集合类型进行操作(如 List、Map、Set 等)。
    java 复制代码
    List<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
  5. 内置函数

    • SpEL 提供了丰富的内置函数,可以用来进行各种常见操作。
    java 复制代码
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression("'Hello'.toUpperCase()");
    System.out.println(exp.getValue());  // 输出 "HELLO"
  6. 引用 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。

  7. 动态属性注入

    • 通过 SpEL,可以动态地设置或获取对象的属性值,甚至支持通过表达式对字段进行操作。

SpEL 的常见应用场景

  1. Spring Bean 配置

    • 在 Spring 的配置文件中,可以使用 SpEL 来动态地设置 Bean 的属性值或条件化 Bean 的创建。
  2. 条件化注入

    • 使用 SpEL 来决定是否注入某个 Bean。例如,基于某些条件才注入 Bean。
  3. 动态查询

    • 在使用 Spring Data 或 Hibernate 时,SpEL 可以帮助动态地构造查询条件。
  4. 配置文件中的属性解析

    • 在 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.propertiesapplication.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或值。

步骤:

  1. application.properties中配置条件值:
properties 复制代码
env=production
  1. 使用@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属性的值:

  • 如果envproduction,则注入'Production Bean'
  • 否则,注入'Development Bean'

application.properties中的env=production时,environmentBean将被注入'Production Bean'

示例 2:基于条件选择Bean

假设你需要根据条件选择不同的Bean进行注入。

步骤:

  1. 创建不同的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.");
    }
}
  1. 使用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表达式来决定注入productionServicedevelopmentService
  • 如果envproductionproductionService会被注入到service中。
  • 如果envdevelopmentdevelopmentService会被注入。

2. 使用Spring的@Conditional注解和SpEL

Spring框架也提供了@Conditional注解,可以基于某些条件来控制Bean的加载。虽然@Conditional本身不是直接与SpEL相关的,但是你可以结合SpEL表达式来做更复杂的条件化控制。

示例:基于SpEL的自定义@Condition

步骤:

  1. 创建自定义条件类:
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);
    }
}
  1. 使用@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,如果是,则加载productionService Bean。
  • 你可以根据条件加载不同的服务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实体,包含nameage属性,且我们希望根据传入的条件来动态地构建查询。

实体类:

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}):如果namenull,则该条件会被忽略(不对name进行过滤),否则会按name进行过滤。
  • (:#{#age} IS NULL OR p.age = :#{#age}):同样的逻辑适用于age,当agenull时不进行过滤。

2. 使用SpEL和Spring Data MongoDB进行动态查询

SpEL也可以和Spring Data MongoDB结合使用,执行动态查询。

示例 2:MongoDB的动态查询

假设我们有一个Product实体,包含nameprice,并希望根据传入的条件动态构建查询。

实体类:

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查询:

  • 如果namenull,则不对name进行筛选。
  • 如果salarynull,则不对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-urlendpoint属性,构造出完整的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 了解了以后,才知道它是一个如此强大工具,熟练掌握它可以提高我们的代码开发效率,将诸多逻辑判断的代码浓缩成一行注解。

相关推荐
BeingACoder6 小时前
【项目实践】公寓租赁项目(九):SpringBoot与Redis整合的快速入门使用
java·spring boot·redis
绝无仅有6 小时前
某游戏大厂Java面试深度解析:从多线程到JVM调优(二)
后端·面试·github
绝无仅有6 小时前
某游戏大厂Java面试指南:Spring、集合与语言特性深度解析 (三)
后端·面试·github
程序新视界6 小时前
数据库的分片与分区:有什么区别?
数据库·后端·mysql
IT_陈寒6 小时前
Java 17 新特性实战:这5个隐藏功能让你的代码效率提升50%
前端·人工智能·后端
程序员爱钓鱼6 小时前
Python编程实战 - 函数与模块化编程 - 创建自己的模块与包
后端
Javatutouhouduan6 小时前
我用ChatGPT,给RabbitMQ加了个连接池
java·spring·rabbitmq·消息中间件·后端开发·java程序员·java八股文
程序员爱钓鱼6 小时前
Python编程实战 - 函数与模块化编程 - Python内置模块(math、os、sys、random等)
后端·python·ipython
Victor3566 小时前
Redis(94)如何启用Redis的数据加密?
后端