Skip to content

使用 @Value (Using @Value)

@Value 通常用于注入外部化的属性:

java
@Component
public class MovieRecommender {

	private final String catalog;

	public MovieRecommender(@Value("${catalog.name}") String catalog) {
		this.catalog = catalog;
	}
}
kotlin
@Component
class MovieRecommender(@Value("\${catalog.name}") private val catalog: String)

配合以下配置:

java
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
kotlin
@Configuration
@PropertySource("classpath:application.properties")
class AppConfig

以及以下的 application.properties 文件:

properties
catalog.name=MovieCatalog

在这种情况下,catalog 参数和字段将被赋值为 MovieCatalog

Spring 提供了一个默认的宽松嵌入式值解析器(lenient embedded value resolver)。它会尝试解析属性值,如果无法解析,则会将属性名称(例如 ${catalog.name})作为值注入。如果你想对不存在的值保持严格控制,你应该声明一个 PropertySourcesPlaceholderConfigurer Bean,如下例所示:

java
@Configuration
public class AppConfig {

	@Bean
	public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
		return new PropertySourcesPlaceholderConfigurer();
	}
}
kotlin
@Configuration
class AppConfig {

	@Bean
	fun propertyPlaceholderConfigurer() = PropertySourcesPlaceholderConfigurer()
}

注意

使用 JavaConfig 配置 PropertySourcesPlaceholderConfigurer 时,@Bean 方法必须是 static 的。

使用上述配置可以确保如果任何 ${} 占位符无法解析,Spring 初始化将失败。还可以使用 setPlaceholderPrefix()setPlaceholderSuffix()setValueSeparator()setEscapeCharacter() 等方法来自定义占位符语法。此外,可以通过 JVM 系统属性(或通过 SpringProperties 机制)设置 spring.placeholder.escapeCharacter.default 属性来全局更改或禁用默认转义字符。

注意

Spring Boot 默认会配置一个 PropertySourcesPlaceholderConfigurer Bean,它会从 application.propertiesapplication.yml 文件中获取属性。

Spring 提供的内置转换器支持允许自动处理简单类型转换(例如转换为 Integerint)。多个逗号分隔的值可以自动转换为 String 数组,无需额外工作。

可以按以下方式提供默认值:

java
@Component
public class MovieRecommender {

	private final String catalog;

	public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
		this.catalog = catalog;
	}
}
kotlin
@Component
class MovieRecommender(@Value("\${catalog.name:defaultCatalog}") private val catalog: String)

Spring 的 BeanPostProcessor 在后台使用 ConversionService 来处理将 @Value 中的 String 值转换为目标类型的过程。如果你想为自己的自定义类型提供转换支持,可以提供自己的 ConversionService Bean 实例,如下例所示:

java
@Configuration
public class AppConfig {

	@Bean
	public ConversionService conversionService() {
		DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
		conversionService.addConverter(new MyCustomConverter());
		return conversionService;
	}
}
kotlin
@Configuration
class AppConfig {

	@Bean
	fun conversionService(): ConversionService {
		return DefaultFormattingConversionService().apply {
			addConverter(MyCustomConverter())
		}
	}
}

@Value 包含 SpEL 表达式时,该值将在运行时动态计算,如下例所示:

java
@Component
public class MovieRecommender {

	private final String catalog;

	public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
		this.catalog = catalog;
	}
}
kotlin
@Component
class MovieRecommender(
	@Value("#{systemProperties['user.catalog'] + 'Catalog' }") private val catalog: String)

SpEL 还支持使用更复杂的数据结构:

java
@Component
public class MovieRecommender {

	private final Map<String, Integer> countOfMoviesPerCatalog;

	public MovieRecommender(
			@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
		this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
	}
}
kotlin
@Component
class MovieRecommender(
	@Value("#{{'Thriller': 100, 'Comedy': 300}}") private val countOfMoviesPerCatalog: Map<String, Int>)

补充教学 —— 玩转 @Value 的必备技能

1. ${} vs #{}:千万别搞混! 这是 Spring 初学者最容易掉进去的坑:

  • ${key} (Property Placeholder):是从环境(Environment)、属性文件、系统变量中找“值”。它只是一个占位符。
  • #{expression} (SpEL expression):是 Spring 表达式。它功能极其强大,可以调用方法、算术运算、访问其他 Bean。
  • 混用:你可以在 SpEL 里面嵌套占位符,比如 @Value("#{'${api.url}' + '/login'}"),这非常灵活。

2. 默认值:防报错神器 生产环境下,如果配置文件漏掉了一个 key,整个容器可能都启动不起来。

  • 语法${key:defaultValue}
  • 进阶:如果你希望默认值是空字符串,写成 ${key:};如果你希望默认值是 null,不好意思,${} 语法本身不支持直接注入 null(通常会视作字符串)。

3. 为什么注入 PropertySourcesPlaceholderConfigurer 必须是 static 这是一个高频面试题。 原理PropertySourcesPlaceholderConfigurer 是一个 BeanFactoryPostProcessor。这类后置处理器必须在容器创建其他 普通 Bean 之前就被实例化并运行。 如果你不把它写成 static,Spring 为了调用这个方法,就必须先实例化你的整个 @Configuration 类。由于实例化配置类涉及很多依赖处理,此时属性占位符还没解析好,就会导致“鸡生蛋、蛋生鸡”的生命周期冲突问题。

4. 数组与集合的自动转换 如果你在 properties 里写了 ids=1,2,3,4。 你直接写 @Value("${ids}") int[] ids; 或是 @Value("${ids}") List<Integer> ids;,Spring 内部的 ConversionService 能够自动帮你完成分割和类型转换。

5. 生产环境建议:@Value vs @ConfigurationProperties

  • @Value:适合注入少量的、孤立的配置项。
  • @ConfigurationProperties:如果是为了映射一组有结构的关系(比如数据库配置、第三方组件的一堆参数),强烈建议使用 @ConfigurationProperties。它支持松散绑定、对象嵌套以及更强的校验功能。

Based on Spring Framework.