6
资源、环境与配置:多环境切换的正确姿势
Resources, Environment & Profiles
开发、测试、生产三套环境,数据库地址不一样、开关不一样、日志级别不一样。你不能靠"上线前手动改配置文件"过日子——那是事故温床。Spring 提供了一套机制:配置外置 + Profile 多环境 + 条件装配,让同一份代码在不同环境下自动加载不同配置。
论四个核心 API
Resource 接口:统一抽象 classpath、文件系统、URL 上的资源,不用关心文件到底在哪。
Environment:当前环境的抽象,能拿到激活的 Profile、系统属性、环境变量。
@Profile:标在 Bean 上,只有指定环境激活时这个 Bean 才会被创建。
@Value / @ConfigurationProperties:把配置文件里的值注入到字段里。
用 @Profile 区分开发和生产的数据源
@Configuration
public class DataSourceConfig {
// 只有 dev 环境激活时才创建这个 DataSource
@Bean
@Profile("dev")
public DataSource devDataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:mysql://localhost:3306/dev_db");
return ds;
}
// 生产环境用另一个
@Bean
@Profile("prod")
public DataSource prodDataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:mysql://prod-db:3306/app_db");
return ds;
}
}
@Value 和 @ConfigurationProperties 注入配置
// 方式一:@Value 一个个注入,适合少量配置
@Component
public class EmailService {
@Value("${mail.host:smtp.example.com}") // 冒号后面是默认值
private String host;
@Value("${mail.port:25}")
private int port;
}
// 方式二:@ConfigurationProperties 批量绑定,推荐
// 配置文件里写:
// app.feature.cache-enabled=true
// app.feature.max-items=100
@Component
@ConfigurationProperties(prefix = "app.feature")
public class FeatureProperties {
private boolean cacheEnabled;
private int maxItems;
// 省略 getter / setter
}
条件装配 @Conditional:满足条件才注册 Bean
// 只有类路径下存在 com.mysql.cj.jdbc.Driver 时,才注册这个 Bean
@Bean
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
public DataSource mysqlDataSource() { ... }
// 只有某个配置项为 true 时才注册
@Bean
@ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")
public CacheManager cacheManager() { ... }
Resource 接口:统一抽象 classpath、文件、URL
Java 标准库读文件很分裂:读 classpath 用 ClassLoader.getResourceAsStream,读磁盘用 FileInputStream,读 HTTP 用 URLConnection——三套 API。Spring 把它们统一成 Resource 接口,你不用关心资源到底在哪。
| 前缀 | 指向哪里 |
classpath:config.xml | 从 classpath 资源(jar 包内)读。 |
file:/etc/app/config.yml | 从文件系统读。 |
https://example.com/x.json | 从 URL 读。 |
无前缀 | 按资源类型自动判断,通常走 classpath。 |
注入 ResourceLoader 读取资源文件
@Service
public class TemplateLoader {
private final ResourceLoader resourceLoader;
public TemplateLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public String load() throws IOException {
// 不管文件在 jar 里还是磁盘上,统一用 getResource 拿
Resource res = resourceLoader.getResource("classpath:templates/welcome.txt");
try (var in = res.getInputStream()) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
}
}
}
Spring Boot 的自动配置原理,本质上就是一堆 @ConditionalOnXxx——类路径下有什么类、配置文件里有什么键,Spring 就决定要不要注册某个 Bean。这是第 9 章要讲的内容。
本章面试题 · 第 6 章(资源与配置)
1.(概念题)@Value("${app.name}") 和 @ConfigurationProperties 有什么区别?为什么后者更推荐?
查看答案
答案:@Value 一个注解只注入一个值,零散;@ConfigurationProperties 把一组配置批量绑定到一个 Java 对象(甚至 record),类型安全、可校验、IDE 能补全。解析:配置项少用 @Value,成组的、要校验的用 @ConfigurationProperties。
2.(实战题)Spring 怎么区分开发/测试/生产环境?
查看答案
答案:@Profile("dev") 标记不同环境的 Bean,配合 spring.profiles.active=dev 激活。配置文件拆成 application-dev.yml、application-prod.yml。解析:数据库地址、日志级别这种环境相关的东西,全按 profile 隔离,别硬编码。