Spring Boot 读取配置文件的多种方法
一、Environment
什么是 Environment ?
Environment 是 SpringBoot 核心的环境配置接口,它提供了简单的方法来访问应用程序属性,包括系统属性、操作系统环境变量、命令行参数、和应用程序配置文件中定义的属性等等。
使用示例
现在项目中有一个 src/main/resources/application.yml 文件:
server:
port: 8787
现在使用 getProperty 方法来读取这个配置:
import org.springframework.core.env.Environment;
@SpringBootTest
@Log4j2
class Springboot3ApplicationTests {
@Autowired
private Environment environment;
@Test
public void readCofig() {
String property = environment.getProperty("server.port");
log.info("getProperty() => {}", property);
}
}
getProperty 方法还可以设置一个默认值:
String property = environment.getProperty("server.port", "默认值");
log.info("getProperty() => {}", property);
如果这个值时必须的可以使用 getRequiredProperty 方法:
// 如果没有这个属性则会抛出 java.lang.IllegalStateException
String requiredProperty = environment.getRequiredProperty("app.name");
log.info("getRequiredProperty() => {}", requiredProperty);
注意事项
- Environment 无法读取自定义配置文件,但是可以结合
@PropertySource(value = "classpath:xxx.properties")
使用。(注意:该注解默认只能指定自定义的 properties 文件,yml 文件会失效) - Environment 只能读取 String 类型的属性值,如果是复杂的属性比如集合、对象等无法读取。
二、@Value
什么是 @Value?
@Value
是 Spring 框架提供的一个强大特性,用于在运行时动态注入配置属性或表达式的值。它可以应用于类的成员变量、方法参数以及构造函数参数上,为Bean的属性赋值提供了一种灵活的方式。
- 表达式解析:
@Value
不仅支持引用配置文件中的属性,还支持SpEL表达式,例如#{T(java.lang.Math).random() * 100}
。这意味着你可以注入动态计算的结果。 - 默认值:为了防止属性未定义时出现异常,可以在表达式中使用default关键字,例如
@Value("${app.version:1.0}")
。 - 属性来源:
@Value
注解中的值可以来源于配置文件、系统环境变量、命令行参数等。确保相关属性已经在 Spring 容器中可用。 - 类型转换:虽然
@Value
主要用于字符串值的注入,但 Spring 会自动进行类型转换,因此你可以直接将@Value
注解用于基本数据类型或包装类的成员变量上。
使用示例
同样还以上面的 application.yml 文件为例:
server:
port: 8787
现在使用 @Value 注解读取这个配置:
注意:使用注解的这个类必须是 Spring 管理的 Bean
import org.springframework.beans.factory.annotation.Value;
@SpringBootTest
@Log4j2
class Springboot3ApplicationTests {
@Value("${server.port}") // 如果不存在这个配置,会直接抛错
private String port;
@Test
public void readCofig() {
log.info("property => {}", port);
}
}
同样的,@Value 也可以设置默认值:
import org.springframework.beans.factory.annotation.Value;
@SpringBootTest
@Log4j2
class Springboot3ApplicationTests {
@Value("${server.port:默认值}") // 或者直接加一个冒号,值可以不写
private String port;
@Test
public void readCofig() {
log.info("property => {}", port);
}
}
注意事项
- 同样的,
@Value
也无法读取自定义配置文件,需要配合@PropertySource
注解使用。 @Value
也无法读取比较复杂的数据类型,比如集合、对象等。
三、 @ConfigurationProperties
什么是 @ConfigurationProperties ?
@ConfigurationProperties
是 Spring Boot 提供的一种强大机制,用于将配置文件中的属性以类型安全的方式绑定到特定的Java类上。这种绑定不仅限于简单的字符串和数字类型,还可以包括复杂的类型,如列表、Map和其他自定义对象。它极大地简化了从配置文件中读取和使用属性的过程,避免了手动解析和类型转换的繁琐。
- 属性绑定:
@ConfigurationProperties
允许你创建一个 Java 类,其中的字段名称对应配置文件中的属性键,这样就可以自动将配置文件中的属性值映射到类的字段上。 - 前缀绑定:你可以指定一个前缀,所有以该前缀开始的配置属性都会绑定到标记了
@ConfigurationProperties
的类的字段上。 - 校验支持:你可以使用 JSR 303/JSR 349 的注解(如 @NotNull, @Min, @Max, @Pattern 等)来对属性进行校验。如果校验失败,Spring Boot 会在启动时抛出异常,阻止应用程序继续运行。
使用示例
现在需要给之前的 application.yml 文件添加一些比较复杂的配置:
server:
port: 8787
user:
username: 张三
password: admin # 这里如果使用下面的开启属性校验模式,启动时就会抛错提示
email: zs@qq.com
endpoints:
api: /api
admin: /admin
orders:
- orderId: 1001
desc: 车子
- orderId: 1002
desc: 房子
- orderId: 1003
desc: 妹子
定义一个用户实体类和订单实体类:
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
//@Builder 注意,如果使用了 @Builder 注解会导致 Bean 创建失败。
@Component // 该类必须是Spring管理的Bean。
@ConfigurationProperties(prefix = "user")
public class UserProperties {
private String username;
private String password;
private String email;
private Map<String, String> endpoints;
private List<OrderDTO> orders;
}
如果需要使用属性校验,可以将 UserProperties 改为下面这样:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.*;
@Data
@Component
@ConfigurationProperties(prefix = "user")
@Validated // 开启属性校验
public class UserProperties {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在6到20个字符之间")
private String password;
@NotBlank(message = "邮箱不能为空")
@Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", message = "邮箱格式不正确")
private String email;
private Map<String, String> endpoints;
private List<OrderDTO> orders;
}
@Data
public class OrderDTO {
private String orderId;
private String desc;
}
现在测试一下是否可以成功读取到这些配置:
@SpringBootTest
@Log4j2
class Springboot3ApplicationTests {
@Autowired
private UserProperties userProperties;
@Test
public void readCofig() {
Map<String, String> endpoints = user.getEndpoints();
log.info("username: {}, password: {}, email: {}", user.getUsername(), user.getPassword(), user.getEmail());
log.info("endpoints => api: {}, admin:{}", endpoints.get("api"), endpoints.get("admin"));
log.info("Orders => {}", user.getOrders());
}
}
注意事项
- 注意 @ConfigurationProperties 不能和 Lombok 的
@Builder
注解一起使用,不然会报错。 - 使用该注解的类必须是Spring管理的Bean,如果类上面没有声明Bean的注解,可以陪着
@EnableConfigurationProperties
注解使用(后面会讲)。
四、@EnableConfigurationProperties
什么是 @EnableConfigurationProperties ?
@EnableConfigurationProperties
是 Spring Boot 提供的一个注解,用于启用和注册具有 @ConfigurationProperties
注解的特定类到 Spring 应用上下文中。这个注解通常应用于 @Configuration
类上,使得带有 @ConfigurationProperties
注解的类可以被 Spring 自动识别和实例化,然后作为 Bean 注入到应用中。
使用示例
结合上面 UserProperties 的例子,将该类上面的@Component
直接去掉启动项目时就会报错,这个时候就可以通过@EnableConfigurationProperties
指定要加载哪些类的配置。
@SpringBootApplication
@EnableConfigurationProperties(UserProperties .class) // 通过这样指定,UserProperties 类上只要有 @ConfigurationProperties 直接就可以了
public class Springboot3Application {
public static void main(String[] args) {
SpringApplication.run(Springboot3Application.class, args);
}
}
利用这个特性,当项目有第三方依赖中的某些类使用 了@ConfigurationProperties
注解但却不是一个Bean时,就可以配合@EnableConfigurationProperties
使它生效。
注意事项
- Bean 的生命周期:使用
@EnableConfigurationProperties
注册的 Bean 默认是单例的(Singleton),这意味着在整个应用的生命周期中只会创建一次实例。 - 自动配置:在大多数情况下,如果你的
@ConfigurationProperties
类已经在类路径中,且没有显式地排除,Spring Boot 会自动配置和注册这些类。只有当需要控制注册哪些类时,才需要使用@EnableConfigurationProperties
。 - 与 @Component 或 @Bean 的区别:
@EnableConfigurationProperties
不是用来创建新的 Bean 的,它只是告诉 Spring Boot 去激活那些已经标记为@ConfigurationProperties
的类。相比之下,@Component
和@Bean
用于创建和定义 Bean。 - 依赖注入:即使没有
@EnableConfigurationProperties
,只要@ConfigurationProperties
类被声明为一个 Bean(例如,通过@Component
),它也可以被注入到其他 Bean 中。但是,@EnableConfigurationProperties
提供了一种更明确的方式来控制哪些配置类被激活。
五、@PropertySources
什么是 @PropertySources ?
@PropertySources
注解的实现原理相对简单,应用程序启动时扫描所有被该注解标注的类,获取到注解中指定自定义配置文件的路径,将指定路径下的配置文件内容加载到 Environment 中,这样可以通过 @Value
注解或 Environment.getProperty()
方法来获取其中定义的属性值了。
- @PropertySource 和 @PropertySources:
@PropertySource
是用来指定一个单独的属性文件,而@PropertySources
则是一个容器注解,可以包含多个@PropertySource
注解,允许你同时加载多个自定义的属性文件。 - 加载顺序:自定义的属性文件通过
@PropertySource
加载时,它们的优先级通常低于默认的配置文件。但是,你可以在@PropertySource
注解中使用ignoreResourceNotFound
属性来控制如果资源找不到时的行为,以及使用order
属性来调整加载顺序。 - 属性绑定:一旦自定义的属性文件被加载,你就可以像使用默认配置文件一样,通过
@Value
注解或Environment.getProperty()
方法来访问这些属性。如果你使用了@ConfigurationProperties
注解,那么这些自定义属性同样会被绑定到相应的 Java Bean 上。 - 注解使用:通常,
@PropertySources
和@PropertySource
注解会放在@Configuration
类上,以确保它们在 Spring 容器初始化时被处理。 - @Profile 注解:结合
@Profile
注解,你可以根据不同的环境加载不同的属性文件,实现更高级的配置管理。 - @ConditionalOnProperty 注解:如果你希望某个配置类或 Bean 只在特定的属性存在或具有特定值时才生效,可以使用
@ConditionalOnProperty
注解。
使用示例
在 src/main/resources 目录下新建两个自定义配置文件:
app.name=SpringBoot3
app.language=JAVA
app2.name=Python
app2.language=Python
以下是一个使用 @PropertySources 的示例:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySources({
@PropertySource(value = "classpath:test1.properties"),
@PropertySource(value = "classpath:test2.properties")
})
public class CustomConfig {
@Value("${app.name:}")
private String appName;
@Value("${app.language:}")
private String appLanguage;
@Value("${app2.name:}")
private String app2Name;
@Value("${app2.language:}")
private String app2Language;
@PostConstruct
public void init(){
System.out.println("appName: " + appName);
System.out.println("appLanguage: " + appLanguage);
System.out.println("app2Name: " + app2Name);
System.out.println("app2Language: " + app2Language);
}
}
在多环境开发中,需要结合不同的环境使用不同的配置,所以可以结合@Profile
注解使用,具体使用方法也很简单,如下:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Profile;
@Configuration
@PropertySources({
@PropertySource(value = "classpath:test1.properties"),
@PropertySource(value = "classpath:test2.properties")
})
@Profile("dev") // 在配置类上加上这个注解
public class CustomConfig {
//......
}
在 application.yml 文件中也要添加一个配置:
spring:
profiles:
active: dev # 这里可以指定环境,如果是dev,那么CustomConfig会生效,如果不是则不会生效。
在实际开发中可能并不会将 spring.profiles.active
的值写死,而是使用 ${...}
的形式去读取系统变量或者环境变量等配置以实现不同环境的配置不同。
注意事项
- 加载顺序:
@PropertySource
的加载顺序是按照它们在@PropertySources
中出现的顺序进行的。后面的属性源可以覆盖前面属性源中的同名属性。 - 资源不存在:如果指定的属性文件不存在,Spring 会抛出一个异常。你可以在
@PropertySource
注解中使用ignoreResourceNotFound
属性来改变这种行为,如果设置为 true,则不会抛出异常。 - 属性覆盖:属性文件之间的属性如果名称相同,后加载的文件中的属性值会覆盖先前加载的文件中的同名属性值。
- 与 @ConfigurationProperties 的结合使用:
@PropertySources
加载的属性可以被@ConfigurationProperties
绑定的类使用,但需要确保属性前缀和类的属性相匹配。 - 环境变量:你可以在属性文件中使用
${...}
占位符来引用环境变量或应用中其他已定义的属性。 - 多环境配置:结合
@Profile
注解,你可以为不同的环境配置加载不同的属性文件,从而实现更灵活的环境配置管理。
使用 @PropertySource 加载 YML 资源
@PropertySource 注解默认是为加载 .properties 文件设计的,它不直接支持 YAML 或者 YML 文件的加载。这是因为 @PropertySource 使用 PropertySourceLoader 来加载资源,而默认的 PropertySourceLoader 只能处理 .properties 文件的格式。
为了使用 @PropertySource 加载 YML 文件,你需要自定义一个 PropertySourceLoader 来处理 YML 文件。这通常涉及到实现 PropertySourceFactory 接口,这个工厂可以将 YML 文件转换为 PropertySource 对象。
以下是一个基本的步骤概述:
创建一个 YamlPropertySourceFactory 类:
- 你需要创建一个类来实现 PropertySourceFactory 接口,这个类将负责读取 YML 文件并转换为 PropertySource。
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.io.IOException;
import java.util.Properties;
public class YamlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}
- 使用 YamlPropertySourceFactory:然后在 @PropertySource 注解中使用你创建的 YamlPropertySourceFactory。例如:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource(value = "classpath:test.yml", factory = YamlPropertySourceFactory.class)
public class MyConfig {
@Value("${app.language:}")
private String appLanguage;
}
通过这种方式,你就能在 Spring 应用中使用 @PropertySource 来加载和使用 YML 文件中的配置信息了。