配置文件加载顺序与优先级规则
⚙️配置文件加载顺序与优先级规则
文章目录
- ⚙️配置文件加载顺序与优先级规则
- 🌐 一、配置加载体系总览:Environment 与 PropertySource
- 🏗️ Spring Boot 配置架构核心组件
- 🔧 Environment 实现类体系
- 📁 二、application.yml 与 bootstrap.yml 的区别
- 🎯 两种配置文件的定位差异
- 🔄 加载时机与顺序对比
- 🔄 三、配置文件加载顺序详解
- 🏆 配置源优先级完整排行榜
- 📊 详细配置源解析
- 🔧 配置属性源附加过程
- 📝 配置文件搜索路径规则
- 🔍 四、ConfigFileApplicationListener 源码解析
- 🎯 环境后处理器核心逻辑
- 🔧 配置文件加载器实现
- 📊 属性源加载器机制
- 💻 五、实战:多环境配置与优先级验证
- 🎯 多环境配置最佳实践
- 🔧 配置优先级验证工具
- 📊 配置覆盖验证测试
- 🐛 六、配置文件冲突与调试技巧
- ⚠️ 常见配置冲突场景
- 🔧 配置冲突调试工具
- 📝 配置调试最佳实践
- 💎 七、总结:让配置更可控、更可预期
- 🎯 配置加载核心规则总结
- 🔧 配置管理最佳实践
- 🚀 高级配置技巧
- 📊 配置性能优化
🌐 一、配置加载体系总览:Environment 与 PropertySource
🏗️ Spring Boot 配置架构核心组件
配置体系的核心接口关系:
🔧 Environment 实现类体系
标准环境实现类结构:
// Spring Boot 中常用的环境实现
public class StandardEnvironment extends AbstractEnvironment {// 系统属性源public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";// 系统环境变量源public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";@Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {// 1. 添加系统属性源propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));// 2. 添加系统环境变量源propertySources.addLast(new MapPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,getSystemEnvironment()));}
}// Web 应用环境扩展
public class StandardServletEnvironment extends StandardEnvironment {public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";@Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {// 先调用父类方法super.customizePropertySources(propertySources);// 添加Servlet相关属性源propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));}
}
📁 二、application.yml 与 bootstrap.yml 的区别
🎯 两种配置文件的定位差异
bootstrap.yml 的特殊地位:
# bootstrap.yml - 用于Spring Cloud上下文引导
spring:application:name: my-application # 应用名称(用于服务发现)cloud:config:uri: http://config-server:8888 # 配置中心地址fail-fast: true # 快速失败retry:initial-interval: 2000 # 重试间隔max-attempts: 10 # 最大重试次数profiles:active: dev # 激活的Profile
application.yml 的应用配置:
# application.yml - 应用级别配置
server:port: 8080 # 服务器端口servlet:context-path: /api # 上下文路径spring:datasource:url: jdbc:mysql://localhost:3306/mydbusername: app_userpassword: ${DB_PASSWORD:default} # 环境变量优先management:endpoints:web:exposure:include: health,info,metrics # 监控端点
🔄 加载时机与顺序对比
配置文件加载时序图:
关键差异总结表:
| 特性 | bootstrap.yml | application.yml |
|---|---|---|
| 加载时机 | 应用上下文创建之前加载 | 应用上下文创建期间加载 |
| 所属上下文 | BootstrapContext(引导上下文) | ApplicationContext(应用上下文) |
| 主要用途 | 外部配置中心连接、系统级配置、加密配置 | 应用业务配置、本地环境配置 |
| 优先级 | 较低(可被 application.yml 覆盖) | 较高(可覆盖 bootstrap.yml) |
| Spring Cloud 角色 | 必需(如 Nacos、Config Server) | 可选 |
| 普通 Spring Boot 项目 | 可选(很少使用) | 必需 |
| 典型内容 | spring.cloud.config.uri、spring.application.name、加密密钥等 | server.port、spring.datasource、业务配置等 |
🔄 三、配置文件加载顺序详解
🏆 配置源优先级完整排行榜
Spring Boot 配置源加载顺序:
graph TBA[配置源] --> B[优先级顺序]B --> C1[1. 命令行参数]B --> C2[2. SPRING_APPLICATION_JSON]B --> C3[3. ServletConfig初始化参数]B --> C4[4. ServletContext初始化参数]B --> C5[5. JNDI属性]B --> C6[6. Java系统属性]B --> C7[7. 操作系统环境变量]B --> C8[8. RandomValuePropertySource]B --> C9[9. Profile特定应用配置]B --> C10[10. 应用配置]B --> C11[11. Profile特定bootstrap配置]B --> C12[12. Bootstrap配置]B --> C13[13. @PropertySource注解]B --> C14[14. 默认属性]style C1 fill:#bbdefb,stroke:#333,stroke-width:2pxstyle C14 fill:#ffccbc,stroke:#333
📊 详细配置源解析
配置源加载源码分析:
public class SpringApplication {/*** 准备环境的核心方法*/private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {// 1. 创建或获取环境实例ConfigurableEnvironment environment = getOrCreateEnvironment();// 2. 配置环境(处理命令行参数等)configureEnvironment(environment, applicationArguments.getSourceArgs());// 3. 触发环境准备事件listeners.environmentPrepared(environment);// 4. 绑定到SpringApplicationbindToSpringApplication(environment);// 5. 环境转换(如果需要)if (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());}// 6. 附加配置属性源ConfigurationPropertySources.attach(environment);return environment;}
}
🔧 配置属性源附加过程
ConfigurationPropertySources.attach() 源码:
public abstract class ConfigurationPropertySources {public static void attach(Environment environment) {MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();// 检查是否已附加if (sources.get(ATTACHED_PROPERTY_SOURCE_NAME) != null) {return;}// 创建ConfigurationPropertySourcesPropertySourceConfigurationPropertySourcesPropertySource attached = new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME, new SpringConfigurationPropertySources(sources));// 插入到属性源列表的最前面sources.addFirst(attached);}
}
📝 配置文件搜索路径规则
Spring Boot 配置文件搜索顺序:
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, Ordered {// 默认搜索路径private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";// 默认配置文件名称private static final String DEFAULT_NAMES = "application";/*** 获取所有可能的配置文件位置*/private Set<String> getSearchLocations() {Set<String> locations = new LinkedHashSet<>();// 1. 用户自定义位置if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {for (String path : asResolvedSet(this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {if (!path.contains("$") && !path.startsWith("classpath:")) {path = "file:" + path;}locations.add(path);}}// 2. 添加默认搜索位置locations.addAll(asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));return locations;}/*** 获取所有可能的配置文件名称*/private Set<String> getSearchNames() {Set<String> names = new LinkedHashSet<>();// 1. 用户自定义名称if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {names.addAll(asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY), null));}// 2. 添加默认名称names.addAll(asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES));return names;}
}
🔍 四、ConfigFileApplicationListener 源码解析
🎯 环境后处理器核心逻辑
ConfigFileApplicationListener 类结构:
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, Ordered {// 配置位置属性键public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";// 配置名称属性键 public static final String CONFIG_NAME_PROPERTY = "spring.config.name";// 附加位置属性键public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";@Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {// 1. 添加属性源占位符addPropertySources(environment, application);// 2. 绑定配置数据bindToSpringApplication(environment, application);}protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {// 获取随机值属性源(用于随机端口等)RandomValuePropertySource.addToEnvironment(environment);// 加载应用配置文件Loader loader = new Loader(environment, resourceLoader);loader.load();}
}
🔧 配置文件加载器实现
Loader 内部类源码分析:
private class Loader {private final ConfigurableEnvironment environment;private final PropertySourceLoader[] loaders;void load() {// 1. 获取激活的ProfileFilteredProfiles filteredProfiles = getProfiles();// 2. 加载默认配置文件(无Profile)Loader.LoadedPropertySources loaded = load("application", filteredProfiles, null);// 3. 加载Profile特定配置文件for (String profile : filteredProfiles.getActive()) {Loader.LoadedPropertySources loadedForProfile = load("application", filteredProfiles, profile);loaded.addAll(loadedForProfile);}// 4. 处理导入的配置processImports(loaded);}private Loader.LoadedPropertySources load(String name, FilteredProfiles filteredProfiles, String profile) {Loader.LoadedPropertySources loaded = new Loader.LoadedPropertySources();// 在所有搜索位置查找配置文件for (String location : getSearchLocations()) {// 检查是否存在配置文件if (!location.endsWith("/")) {location = location + "/";}for (PropertySourceLoader loader : this.loaders) {// 尝试加载不同格式的配置文件for (String extension : loader.getFileExtensions()) {Loader.LoadedPropertySources loadedFromLocation = load(loader, location + name, profile, extension, filteredProfiles);loaded.addAll(loadedFromLocation);}}}return loaded;}
}
📊 属性源加载器机制
PropertySourceLoader 接口实现:
public interface PropertySourceLoader {/*** 支持的文件扩展名*/String[] getFileExtensions();/*** 加载属性源*/PropertySource<?> load(String name, Resource resource, String profile) throws IOException;
}// YAML属性源加载器实现
public class YamlPropertySourceLoader implements PropertySourceLoader {@Overridepublic String[] getFileExtensions() {return new String[] { "yml", "yaml" };}@Overridepublic PropertySource<?> load(String name, Resource resource, String profile) throws IOException {// 解析YAML文件List<Document> documents = YamlDocumentLoader.load(resource, profile);if (documents.isEmpty()) {return null;}// 创建属性源return new OriginTrackedMapPropertySource(name, getFlattenedMap(documents));}
}// Properties属性源加载器实现
public class PropertiesPropertySourceLoader implements PropertySourceLoader {@Overridepublic String[] getFileExtensions() {return new String[] { "properties", "xml" };}@Overridepublic PropertySource<?> load(String name, Resource resource, String profile) throws IOException {// 解析Properties文件Properties properties = PropertiesLoaderUtils.loadProperties(resource);if (properties.isEmpty()) {return null;}return new PropertiesPropertySource(name, properties);}
}
💻 五、实战:多环境配置与优先级验证
🎯 多环境配置最佳实践
完整的配置文件结构:
src/main/resources/
├── application.yml # 主配置文件(默认配置)
├── application-dev.yml # 开发环境配置
├── application-test.yml # 测试环境配置
├── application-prod.yml # 生产环境配置
└── bootstrap.yml # 引导配置(可选)
主配置文件示例:
# application.yml - 默认配置
spring:profiles:active: dev # 默认激活开发环境server:port: 8080servlet:context-path: /apilogging:level:root: INFOcom.example: DEBUG# 公共数据源配置(可被环境特定配置覆盖)
database:pool-size: 10timeout: 30000
环境特定配置示例:
# application-dev.yml - 开发环境
spring:datasource:url: jdbc:h2:mem:testdbusername: sapassword: driver-class-name: org.h2.Driverlogging:level:com.example: TRACEorg.hibernate.SQL: DEBUGdebug: true # 启用调试模式---
# application-test.yml - 测试环境
spring:datasource:url: jdbc:mysql://test-db:3306/app_testusername: testerpassword: test123driver-class-name: com.mysql.cj.jdbc.Drivermanagement:endpoints:web:exposure:include: health,info,metrics---
# application-prod.yml - 生产环境
spring:datasource:url: jdbc:mysql://prod-db-cluster:3306/app_produsername: ${DB_USERNAME}password: ${DB_PASSWORD}driver-class-name: com.mysql.cj.jdbc.Driverhikari:maximum-pool-size: 20connection-timeout: 10000management:endpoints:web:exposure:include: health # 生产环境只暴露健康检查logging:level:root: WARNcom.example: INFO
🔧 配置优先级验证工具
配置源调试工具类:
@Component
@Slf4j
public class ConfigurationDebugger implements ApplicationRunner {@Autowiredprivate ConfigurableEnvironment environment;@Overridepublic void run(ApplicationArguments args) throws Exception {logConfigurationSources();testPropertyPrecedence();}/*** 打印所有配置源及其优先级*/private void logConfigurationSources() {log.info("=== 配置源优先级报告 ===");MutablePropertySources propertySources = environment.getPropertySources();int index = 1;for (PropertySource<?> propertySource : propertySources) {log.info("{}. {} [{}]", index++, propertySource.getName(), propertySource.getClass().getSimpleName());// 显示前几个属性示例if (propertySource instanceof EnumerablePropertySource) {EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) propertySource;String[] propertyNames = enumerable.getPropertyNames();int sampleSize = Math.min(propertyNames.length, 3);for (int i = 0; i < sampleSize; i++) {String name = propertyNames[i];Object value = propertySource.getProperty(name);log.info(" {} = {}", name, value);}if (propertyNames.length > sampleSize) {log.info(" ... 还有 {} 个属性", propertyNames.length - sampleSize);}}log.info(" ---");}}/*** 测试属性优先级*/private void testPropertyPrecedence() {log.info("=== 属性优先级测试 ===");String[] testProperties = {"server.port","spring.datasource.url", "logging.level.root","management.endpoints.web.exposure.include"};for (String property : testProperties) {String value = environment.getProperty(property);String source = findPropertySource(property);log.info("{} = {} [来源: {}]", property, value, source);}}/*** 查找属性来源*/private String findPropertySource(String propertyName) {MutablePropertySources propertySources = environment.getPropertySources();for (PropertySource<?> propertySource : propertySources) {if (propertySource.containsProperty(propertyName)) {return propertySource.getName();}}return "未找到";}
}
📊 配置覆盖验证测试
多环境配置覆盖测试:
@SpringBootTest
@ActiveProfiles({"dev", "test"}) // 激活多个Profile测试覆盖规则
class ConfigurationPrecedenceTest {@Autowiredprivate ConfigurableEnvironment environment;@Testvoid testProfilePrecedence() {// 测试Profile配置覆盖规则assertThat(environment.getProperty("spring.datasource.url")).isEqualTo("jdbc:mysql://test-db:3306/app_test"); // test覆盖devassertThat(environment.getProperty("logging.level.com.example")).isEqualTo("TRACE"); // dev配置(test中没有此配置)}@Testvoid testCommandLineArgumentsPrecedence() {// 模拟命令行参数SimpleCommandLinePropertySource commandLineSource = new SimpleCommandLinePropertySource("--server.port=9090", "--debug=true");environment.getPropertySources().addFirst(commandLineSource);// 命令行参数应该具有最高优先级assertThat(environment.getProperty("server.port")).isEqualTo("9090");assertThat(environment.getProperty("debug")).isEqualTo("true");}@Testvoid testSystemPropertiesPrecedence() {// 设置系统属性System.setProperty("app.custom.setting", "system-value");// 系统属性应该覆盖配置文件assertThat(environment.getProperty("app.custom.setting")).isEqualTo("system-value");}
}
🐛 六、配置文件冲突与调试技巧
⚠️ 常见配置冲突场景
配置覆盖冲突示例:
# 冲突场景1:多Profile配置冲突
# application-common.yml
app:feature:enabled: truetimeout: 5000# application-dev.yml
app:feature:timeout: 10000 # 覆盖common配置# application-prod.yml
app:feature:enabled: false # 覆盖common配置timeout: 30000 # 覆盖dev配置# 冲突场景2:多文件配置冲突
# application.yml
database:host: localhostport: 3306# config/application.yml(更高优先级)
database:host: 192.168.1.100 # 覆盖classpath下的配置
🔧 配置冲突调试工具
配置冲突检测器:
@Component
@Slf4j
public class ConfigurationConflictDetector {@Autowiredprivate ConfigurableEnvironment environment;@EventListenerpublic void detectConflicts(ApplicationReadyEvent event) {log.info("=== 配置冲突检测 ===");detectDuplicateProperties();detectProfileConflicts();detectTypeMismatches();}/*** 检测重复属性定义*/private void detectDuplicateProperties() {Map<String, List<String>> propertySourcesMap = new HashMap<>();MutablePropertySources sources = environment.getPropertySources();// 收集所有属性及其来源for (PropertySource<?> source : sources) {if (source instanceof EnumerablePropertySource) {EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;for (String propertyName : enumerable.getPropertyNames()) {propertySourcesMap.computeIfAbsent(propertyName, k -> new ArrayList<>()).add(source.getName());}}}// 报告重复属性propertySourcesMap.entrySet().stream().filter(entry -> entry.getValue().size() > 1).forEach(entry -> {log.warn("🔴 属性冲突: {} 在多个配置源中定义: {}", entry.getKey(), entry.getValue());// 显示实际值String value = environment.getProperty(entry.getKey());log.info(" 当前生效值: {} = {}", entry.getKey(), value);});}/*** 检测Profile配置冲突*/private void detectProfileConflicts() {String[] activeProfiles = environment.getActiveProfiles();if (activeProfiles.length > 1) {log.info("激活的Profile: {}", Arrays.toString(activeProfiles));log.warn("多个Profile激活,注意配置覆盖规则:后面的Profile会覆盖前面的");}}
}
📝 配置调试最佳实践
Spring Boot 配置调试配置:
# 开启详细配置日志
logging.level.org.springframework.boot.env=DEBUG
logging.level.org.springframework.core.env=TRACE# 显示配置加载详情
debug=true
自定义配置调试端点:
@RestController
@Endpoint(id = "config")
@Slf4j
public class ConfigurationEndpoint {@Autowiredprivate ConfigurableEnvironment environment;@ReadOperationpublic Map<String, Object> getConfigurationDetails() {Map<String, Object> details = new LinkedHashMap<>();// 1. 配置源信息details.put("propertySources", getPropertySourceDetails());// 2. 激活的Profiledetails.put("activeProfiles", environment.getActiveProfiles());// 3. 默认Profiledetails.put("defaultProfiles", environment.getDefaultProfiles());// 4. 关键配置值details.put("keyProperties", getKeyProperties());return details;}private List<Map<String, Object>> getPropertySourceDetails() {List<Map<String, Object>> sources = new ArrayList<>();MutablePropertySources propertySources = environment.getPropertySources();for (PropertySource<?> source : propertySources) {Map<String, Object> sourceInfo = new HashMap<>();sourceInfo.put("name", source.getName());sourceInfo.put("type", source.getClass().getSimpleName());if (source instanceof EnumerablePropertySource) {EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;sourceInfo.put("propertyCount", enumerable.getPropertyNames().length);}sources.add(sourceInfo);}return sources;}private Map<String, String> getKeyProperties() {String[] keyProperties = {"server.port","spring.datasource.url","spring.application.name","logging.level.root"};Map<String, String> values = new HashMap<>();for (String property : keyProperties) {values.put(property, environment.getProperty(property));}return values;}
}
💎 七、总结:让配置更可控、更可预期
🎯 配置加载核心规则总结
Spring Boot 配置优先级黄金法则:
| 优先级 | 配置源 | 覆盖能力 | 典型使用场景 |
|---|---|---|---|
| 1️⃣ | 命令行参数 (--server.port=9090) | ⭐⭐⭐⭐(最强) | 临时参数、容器部署、测试调试 |
| 2️⃣ | Java 系统属性 (-Dserver.port=9090) | ⭐⭐⭐⭐ | JVM 启动参数、系统级设置 |
| 3️⃣ | 操作系统环境变量 (SERVER_PORT=9090) | ⭐⭐⭐ | Docker、K8s、CI/CD |
| 4️⃣ | Profile 特定应用配置 (application-dev.yml) | ⭐⭐ | 环境差异化配置(dev、test、prod) |
| 5️⃣ | 应用配置文件 (application.yml) | ⭐⭐ | 默认业务配置、通用属性 |
| 6️⃣ | Profile 特定 Bootstrap 配置 (bootstrap-dev.yml) | ⭐ | Spring Cloud 环境配置 |
| 7️⃣ | Bootstrap 配置 (bootstrap.yml) | ⭐(最弱) | 引导阶段配置、远程配置中心接入 |
🔧 配置管理最佳实践
多环境配置策略:
# 1. 使用Profile进行环境隔离
spring:profiles:active: @activatedProperties@ # Maven过滤替换# 2. 分层配置结构
# application.yml - 基础配置
app:version: 1.0.0base-config: value# application-{env}.yml - 环境特定配置
app:env-specific: value# application-{feature}.yml - 功能模块配置
feature:module:enabled: true# 3. 安全配置分离
# bootstrap.yml - 敏感配置(可加密)
encrypt:key: ${ENCRYPT_KEY}
spring:cloud:config:token: ${CONFIG_TOKEN}
配置验证与防护:
@Configuration
@ConfigurationProperties(prefix = "app")
@Validated // 启用JSR-303验证
@Data
public class AppConfig {@NotNullprivate String name;@Min(1)@Max(65535)private Integer port;@Pattern(regexp = "^https?://.*")private String url;@Valid // 嵌套验证private DatabaseConfig database;@Datapublic static class DatabaseConfig {@NotEmptyprivate String host;@Min(1)@Max(65535) private Integer port;}
}// 配置健康检查
@Component
public class ConfigurationHealthIndicator implements HealthIndicator {@Autowiredprivate AppConfig appConfig;@Overridepublic Health health() {try {// 验证配置完整性validateConfiguration();return Health.up().withDetail("configStatus", "VALID").build();} catch (ConfigurationException e) {return Health.down().withDetail("configStatus", "INVALID").withDetail("error", e.getMessage()).build();}}
}
🚀 高级配置技巧
动态配置更新机制:
@Configuration
@Slf4j
public class DynamicConfiguration {@Autowiredprivate ConfigurableEnvironment environment;/*** 运行时更新配置*/@Scheduled(fixedRate = 30000) // 每30秒检查一次public void reloadExternalConfiguration() {try {// 从外部源加载最新配置Map<String, Object> newConfig = loadConfigFromExternalSource();// 创建新的属性源MapPropertySource newSource = new MapPropertySource("dynamic-config", newConfig);// 替换或添加属性源MutablePropertySources sources = environment.getPropertySources();if (sources.contains("dynamic-config")) {sources.replace("dynamic-config", newSource);} else {sources.addFirst(newSource); // 最高优先级}log.info("动态配置更新完成");} catch (Exception e) {log.error("动态配置更新失败", e);}}
}
📊 配置性能优化
配置加载性能监控:
@Component
@Slf4j
public class ConfigurationPerformanceMonitor {private long configLoadStartTime;@EventListenerpublic void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {this.configLoadStartTime = System.currentTimeMillis();}@EventListener public void onApplicationEvent(ApplicationReadyEvent event) {long configLoadTime = System.currentTimeMillis() - configLoadStartTime;log.info("=== 配置加载性能报告 ===");log.info("配置加载总耗时: {}ms", configLoadTime);// 详细性能分析analyzeConfigurationPerformance(event.getApplicationContext());}private void analyzeConfigurationPerformance(ApplicationContext context) {if (context instanceof ConfigurableApplicationContext) {ConfigurableEnvironment env = ((ConfigurableApplicationContext) context).getEnvironment();MutablePropertySources sources = env.getPropertySources();log.info("加载的配置源数量: {}", sources.size());sources.forEach(source -> {if (source instanceof OriginTrackedMapPropertySource) {log.debug("配置源: {} - 包含 {} 个属性", source.getName(), ((OriginTrackedMapPropertySource) source).getPropertyNames().length);}});}}
}
