Spring中的属性占位符
属性占位符(Property Placeholder)是 Spring 框架中一个非常实用的特性,它主要用于从配置源中动态获取属性值,并将这些值注入到应用程序的组件中。
下面将从基本概念、工作原理、配置方式、使用场景以及与 SpEL 的对比等方面详细介绍属性占位符。
# 基本概念
属性占位符使用 ${}
作为定界符,格式通常为 ${property.name:defaultValue}
。其中,property.name
是要查找的属性名,defaultValue
是可选的默认值,当在配置源中找不到指定属性时,就会使用这个默认值。
# 工作原理
当 Spring 容器在处理带有属性占位符的注解(如 @Value
)或配置文件时,会触发属性占位符解析机制。具体步骤如下:
- 解析占位符:Spring 会识别
${}
包裹的内容,并提取出属性名。 - 查找属性值:从配置源中查找该属性名对应的值。配置源可以是多种类型,如
application.properties
、application.yml
文件,系统环境变量,Java 系统属性等。 - 使用默认值(可选):如果在配置源中找不到该属性名对应的值,且占位符中指定了默认值,那么就会使用这个默认值。
- 注入属性值:将找到的属性值或默认值注入到对应的字段、方法参数或配置项中。
# 配置方式
# 1. 使用 application.properties
文件
在 Spring Boot 应用中,最常见的配置方式是使用 application.properties
文件。例如:
# application.properties
database.url=jdbc:mysql://localhost:3306/mydb
database.username=root
database.password=secret
在 Java 代码中可以使用 @Value
注解来注入这些属性值:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class DatabaseConfig {
@Value("${database.url}")
private String url;
@Value("${database.username}")
private String username;
@Value("${database.password}")
private String password;
// Getters and setters
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
# 2. 使用 application.yml
文件
application.yml
文件也是常用的配置文件格式,它具有更清晰的层级结构。例如:
# application.yml
database:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: secret
Java 代码的使用方式与 application.properties
相同。
# 3. 系统环境变量和 Java 系统属性
除了配置文件,属性占位符还可以从系统环境变量和 Java 系统属性中获取值。例如,设置系统环境变量 MY_APP_CONFIG=production
,在 Java 代码中可以这样使用:
@Value("${MY_APP_CONFIG:development}")
private String appConfig;
# 使用场景
# 1. 配置数据库连接信息
如上述示例所示,将数据库的 URL、用户名和密码等信息配置在属性文件中,通过属性占位符注入到应用程序中,方便在不同环境下进行配置切换。
# 2. 多环境配置
在开发、测试和生产环境中,很多配置项可能不同。可以使用不同的配置文件(如 application-dev.properties
、application-test.properties
、application-prod.properties
),并通过 spring.profiles.active
属性指定当前使用的环境,实现不同环境下的配置隔离。
# 3. 动态配置参数
应用程序中可能有一些需要动态调整的参数,如缓存过期时间、日志级别等。将这些参数配置在属性文件中,通过属性占位符注入到相应的组件中,方便后续修改和管理。
# 与 SpEL 的对比
# 1. 语法区别
- 属性占位符使用
${}
作为定界符,主要用于从配置源中获取属性值。 - SpEL 使用
#{}
作为定界符,可以进行更复杂的表达式计算,如属性访问、方法调用、算术和逻辑运算等。
# 2. 功能区别
- 属性占位符主要用于配置信息的注入,侧重于从配置源中获取静态的属性值。
- SpEL 更侧重于在运行时进行动态计算和逻辑处理,可以结合属性占位符一起使用,实现更灵活的配置和计算。例如:
@Value("#{${someValue} + 1}")
private int calculatedValue;
这里先使用属性占位符获取 someValue
的值,然后在 SpEL 表达式中对其进行运算。
# 涉及的 Spring 内部类
# 1. PropertySourcesPlaceholderConfigurer
- 作用:在 Spring 传统配置中,
PropertySourcesPlaceholderConfigurer
是一个非常重要的后置处理器(BeanFactoryPostProcessor
)。它负责处理属性占位符,会在 Bean 定义加载完成后、Bean 实例化之前,对 Bean 定义中的属性占位符进行解析和替换。 - 原理:该类会从多个属性源(如
application.properties
、环境变量等)中查找属性值,将配置文件或注解中的${}
占位符替换为实际的属性值。
# 2. PropertySources
- 作用:
PropertySources
是一个存储多个PropertySource
的集合接口。它可以包含不同类型的属性源,如PropertiesPropertySource
(基于java.util.Properties
的属性源)、SystemEnvironmentPropertySource
(基于系统环境变量的属性源)等。 - 原理:在属性占位符解析过程中,
PropertySourcesPlaceholderConfigurer
会遍历PropertySources
中的各个PropertySource
,依次查找所需的属性值。
# 3. PropertySource
- 作用:
PropertySource
是一个抽象类,代表一个属性源,它定义了从属性源中获取属性值的基本方法。不同的具体实现类对应不同类型的属性源,例如PropertiesPropertySource
用于从Properties
对象中获取属性值,SystemEnvironmentPropertySource
用于从系统环境变量中获取属性值。 - 原理:当
PropertySourcesPlaceholderConfigurer
需要查找某个属性值时,会调用PropertySource
的getProperty
方法,该方法会根据属性名返回对应的属性值。
# 4. StandardEnvironment
- 作用:
StandardEnvironment
是 Spring 环境抽象的默认实现,它继承自AbstractEnvironment
类。该类负责管理应用程序的属性源集合PropertySources
,并提供了一些方便的方法来访问和操作这些属性源。 - 原理:在 Spring 应用启动时,会创建一个
StandardEnvironment
实例,并将默认的属性源(如系统属性、系统环境变量)添加到PropertySources
中。在属性占位符解析过程中,PropertySourcesPlaceholderConfigurer
会使用StandardEnvironment
中的PropertySources
来查找属性值。
# 内部原理
# 1. 启动阶段
- 环境初始化:在 Spring 应用启动时,会创建一个
StandardEnvironment
实例,并初始化其PropertySources
集合,将系统属性和系统环境变量等默认属性源添加到集合中。 - 配置文件加载:如果使用了
application.properties
或application.yml
等配置文件,Spring 会在启动过程中加载这些文件,并将其中的属性信息封装成PropertySource
对象,添加到PropertySources
集合中。
# 2. Bean 定义加载阶段
- 占位符识别:当 Spring 加载 Bean 定义时,会识别其中的属性占位符(如
${property.name}
)。这些占位符可能出现在@Value
注解、XML 配置文件等地方。
# 3. Bean 工厂后置处理阶段
PropertySourcesPlaceholderConfigurer
执行:PropertySourcesPlaceholderConfigurer
作为一个BeanFactoryPostProcessor
,会在 Bean 实例化之前被调用。它会遍历 Bean 定义中的所有属性,查找其中的属性占位符。- 属性值查找:对于每个属性占位符,
PropertySourcesPlaceholderConfigurer
会从StandardEnvironment
的PropertySources
集合中依次查找对应的属性值。它会调用每个PropertySource
的getProperty
方法,直到找到属性值或遍历完所有的PropertySource
。 - 占位符替换:如果找到了属性值,
PropertySourcesPlaceholderConfigurer
会将属性占位符替换为实际的属性值。如果没有找到属性值,但占位符指定了默认值,则使用默认值进行替换;如果没有指定默认值,则会抛出异常。
# 4. Bean 实例化阶段
- 属性注入:经过占位符替换后,Spring 会根据更新后的 Bean 定义实例化 Bean,并将解析后的属性值注入到 Bean 的相应字段或方法参数中。
以下是一个简单的示例代码,展示了属性占位符的使用和相关原理:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
@Configuration
public class AppConfig {
@Value("${message:Default Message}")
private String message;
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
public MyBean myBean() {
return new MyBean(message);
}
}
class MyBean {
private String message;
public MyBean(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyBean myBean = context.getBean(MyBean.class);
System.out.println(myBean.getMessage());
context.close();
}
}
在这个示例中,PropertySourcesPlaceholderConfigurer
负责解析 @Value
注解中的属性占位符,从属性源中查找 message
属性的值,并将其注入到 MyBean
中。
祝你变得更强!