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

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

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

SpEL 的基本功能

  1. 简单计算

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

    • 可以通过表达式直接访问对象的属性或方法。
    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)等。
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression("true and false");
    System.out.println(exp.getValue());  // 输出 false
    
  4. 集合操作

    • SpEL 可以方便地对集合类型进行操作(如 List、Map、Set 等)。
    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 提供了丰富的内置函数,可以用来进行各种常见操作。
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression("'Hello'.toUpperCase()");
    System.out.println(exp.getValue());  // 输出 "HELLO"
    
  6. 引用 Spring Bean

    • 在 Spring 上下文中,可以通过 SpEL 引用和操作 Spring Bean。
    <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的属性值。

示例:

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://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表达式。

示例:

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 Setterpublic 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文件。

示例:

# application.properties
salary=1000
multiplier=1.5
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class Employee {@Value("#{${salary} * ${multiplier}}")private double salary;// Getter and Setterpublic double getSalary() {return salary;}public void setSalary(double salary) {this.salary = salary;}
}

4. SpEL常见操作符

  • 数学运算+, -, *, /, % 等。
  • 逻辑运算and, or, not
  • 集合操作.size(), .isEmpty(), .contains(), .toArray()等。
  • 引用类型T(className):引用Java类型。
  • 条件判断?::类似于三元运算符。

示例:

<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中配置条件值:
env=production
  1. 使用@Value注解配合SpEL表达式进行条件化注入:
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:
@Component
public class ProductionService implements IService {@Overridepublic void execute() {System.out.println("Running in production environment.");}
}@Component
public class DevelopmentService implements IService {@Overridepublic void execute() {System.out.println("Running in development environment.");}
}
  1. 使用SpEL选择性注入:
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. 创建自定义条件类:
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;public class OnProductionCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {String env = context.getEnvironment().getProperty("env");return "production".equals(env);}
}
  1. 使用@Conditional注解来控制Bean的加载:
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
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属性,且我们希望根据传入的条件来动态地构建查询。

实体类:

import javax.persistence.Entity;
import javax.persistence.Id;@Entity
public class Person {@Idprivate Long id;private String name;private int age;// Getter and Setter methods
}

动态查询接口:

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,并希望根据传入的条件动态构建查询。

实体类:

import org.springframework.data.annotation.Id;public class Product {@Idprivate String id;private String name;private double price;// Getter and Setter methods
}

动态查询接口:

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来执行动态查询,例如:

@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动态查询

实体类:

public class Employee {private Long id;private String name;private int salary;// Getter and Setter methods
}

动态查询接口:

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的动态查询
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

myapp.username=admin
myapp.password=secret
myapp.url=https://example.com

2. 使用@Value注解结合SpEL注入属性

可以使用@Value注解结合SpEL从配置文件中读取属性。使用${}符号来引用配置文件中的属性,然后通过SpEL解析。

示例:

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

myapp:username: adminpassword: secreturl: https://example.com

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

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类:

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

myapp.featureEnabled=true

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 了解了以后,才知道它是一个如此强大工具,熟练掌握它可以提高我们的代码开发效率,将诸多逻辑判断的代码浓缩成一行注解。

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

相关文章:

  • IntelliJ IDEA配置Tomcat教程
  • 北京做网站建设的公司排名网页qq登陆保护在哪里
  • 广东省网站集约化建设做外贸如何建立网站
  • Rust 中的 if let 与 while let 语法糖:简化模式匹配的优雅工具
  • 基于GBR原理的叠腮技术生物学基础
  • 网站备案找哪个部门制作一个网站能多少钱
  • 汕头站扩建进展成都开发小程序的公司
  • 保健品 东莞网站建设欧米茄表官方官网
  • 郑州网站建设彳汉狮网络在线制图网
  • Rust Vec 的内存布局与扩容策略:从底层实现到性能优化
  • Hive 分区表变更字段长度不生效
  • 成都企业网站怎么做广东网络推广项目
  • 沈阳网站建设公司怎么样服装店的营销方法
  • 网站建设网络科技公司加盟水区建设局网站
  • 【企业SRE/DevOps向的精通Linux课程培训课程】第 18 天:Web 服务器(Apache、Nginx、反向代理)
  • 电商网站免费设计有关网站建设新闻资讯
  • 常用链地址 区块链常用浏览器地址
  • 手机网站推荐哪些提升关键词优化排名软件
  • 东莞网站建设 烤活鱼三门峡网站建设推广
  • csapp实验一:datalab
  • 两个不同git仓库,如何合并1个git仓库的提交到另1个仓库?
  • 南通网站建设top公司邮箱怎么申请的
  • 网站建设 数据上传 查询歌曲网站源码
  • Kubernetes 常见问题全解析
  • 网站建设合同有哪些杭州小程序开发
  • C++ 单调队列
  • 湖南省建设工程造价管理总站微网站是官网的手机站
  • 上证50期权的到期日期是什么时候?
  • 网站开发看谁的教程成都企业模板网站开发
  • 【.NET】WinForm中如何调整DataGridView控件的列宽?