Spring Boot 3.x Bootstrap上下文与主上下文配置冲突深度解析

一、问题现象与核心原因

1.1 典型冲突场景

// 场景1:Bootstrap上下文中的Bean与主上下文重复
// Bootstrap配置类
@Configuration
public class BootstrapConfig {
    
    @Bean
    public DataSource bootstrapDataSource() {
        // Bootstrap上下文的数据源
        return new HikariDataSource();
    }
}

// 主应用配置类
@SpringBootApplication
public class MainApplication {
    
    @Bean
    public DataSource mainDataSource() {
        // 主上下文的数据源
        return new HikariDataSource();
    }
}

// 场景2:属性配置覆盖冲突
// bootstrap.yml
spring:
  datasource:
    url: jdbc:h2:mem:bootstrap
    username: bootstrap

// application.yml  
spring:
  datasource:
    url: jdbc:h2:mem:main
    username: main

// 场景3:自动配置重复执行
// Bootstrap上下文中触发的自动配置可能被主上下文重复执行

1.2 典型错误现象

// 错误1:Bean名称冲突
***************************
APPLICATION FAILED TO START
***************************

Description:
The bean 'dataSource', defined in class path resource [com/example/BootstrapConfig.class],
could not be registered. A bean with that name has already been defined in class path resource 
[com/example/MainApplication.class] and overriding is disabled.

// 错误2:配置优先级错误
2024-01-20T10:30:00.000Z  WARN 12345 --- [           main] o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext : 
Exception encountered during context initialization - cancelling refresh attempt: 
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource': 
Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
No qualifying bean of type 'javax.sql.DataSource' available: expected single matching bean but found 2: bootstrapDataSource,mainDataSource

// 错误3:属性值混乱
// 实际使用的配置值不符合预期,可能是bootstrap的,也可能是application的

1.3 根本原因分析

Spring Boot 3.x中Bootstrap上下文与主上下文配置冲突的核心原因:

  1. 上下文层次结构 - Bootstrap上下文是主上下文的父上下文
  2. Bean定义继承 - 父上下文的Bean对子上下文可见
  3. 属性源合并 - 多个环境的属性源合并规则复杂
  4. 自动配置重复 - 自动配置类在两个上下文都可能被触发
  5. Profile处理差异 - 不同上下文的Profile激活机制不同

二、诊断工具与方法

2.1 启用上下文层次结构跟踪

# application.yml
logging:
  level:
    org.springframework.boot.SpringApplication: DEBUG
    org.springframework.context.ApplicationContext: DEBUG
    org.springframework.cloud.bootstrap: TRACE
    org.springframework.core.env: DEBUG
  pattern:
    console: "%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n"

debug: true

management:
  endpoints:
    web:
      exposure:
        include: beans, env, configprops
  endpoint:
    beans:
      enabled: true
    env:
      enabled: true

2.2 上下文层次诊断器

@Component
public class ContextHierarchyDiagnostic implements ApplicationRunner {
    
    private static final Logger log = LoggerFactory.getLogger(ContextHierarchyDiagnostic.class);
    
    @Autowired
    private ApplicationContext applicationContext;
    
    @Override
    public void run(ApplicationArguments args) {
        diagnoseContextHierarchy();
        compareBeanDefinitions();
        analyzePropertySources();
        checkConfigurationConflicts();
    }
    
    private void diagnoseContextHierarchy() {
        log.info("=== 上下文层次结构诊断 ===");
        
        ApplicationContext current = applicationContext;
        int level = 0;
        
        while (current != null) {
            log.info("层级 {}: {}", level, current.getClass().getName());
            log.info("  ID: {}", current.getId());
            log.info("  父上下文: {}", current.getParent() != null ? 
                current.getParent().getId() : "无");
            
            // 检查是否是Bootstrap上下文
            if (isBootstrapContext(current)) {
                log.info("  ✅ 检测到Bootstrap上下文");
            }
            
            // 显示Bean数量
            if (current instanceof ConfigurableApplicationContext) {
                ConfigurableApplicationContext configContext = 
                    (ConfigurableApplicationContext) current;
                int beanCount = configContext.getBeanDefinitionNames().length;
                log.info("  Bean定义数量: {}", beanCount);
            }
            
            current = current.getParent();
            level++;
        }
        
        // 检查是否有重复的上下文
        checkDuplicateContexts();
    }
    
    private boolean isBootstrapContext(ApplicationContext context) {
        // 通过ID或Bean名称判断是否是Bootstrap上下文
        String id = context.getId();
        return id != null && 
               (id.contains("bootstrap") || 
                id.contains("Bootstrap") ||
                hasBootstrapBeans(context));
    }
    
    private boolean hasBootstrapBeans(ApplicationContext context) {
        try {
            String[] beanNames = context.getBeanDefinitionNames();
            return Arrays.stream(beanNames)
                .anyMatch(name -> name.contains("bootstrap") || 
                                  name.contains("Bootstrap"));
        } catch (Exception e) {
            return false;
        }
    }
    
    private void compareBeanDefinitions() {
        log.info("=== Bean定义比较 ===");
        
        Map<String, List<String>> beanToContexts = new HashMap<>();
        
        // 收集所有上下文中的Bean
        collectBeansFromAllContexts(applicationContext, beanToContexts);
        
        // 找出重复的Bean
        beanToContexts.entrySet().stream()
            .filter(entry -> entry.getValue().size() > 1)
            .forEach(entry -> {
                log.warn("⚠️ Bean '{}' 在多个上下文中定义:", entry.getKey());
                entry.getValue().forEach(context -> 
                    log.warn("  上下文: {}", context));
            });
    }
    
    private void collectBeansFromAllContexts(ApplicationContext context, 
                                            Map<String, List<String>> beanToContexts) {
        if (context == null) return;
        
        String contextId = context.getId();
        String[] beanNames = context.getBeanDefinitionNames();
        
        for (String beanName : beanNames) {
            beanToContexts.computeIfAbsent(beanName, k -> new ArrayList<>())
                .add(contextId);
        }
        
        // 递归处理父上下文
        collectBeansFromAllContexts(context.getParent(), beanToContexts);
    }
    
    private void analyzePropertySources() {
        log.info("=== 属性源分析 ===");
        
        ConfigurableEnvironment env = 
            (ConfigurableEnvironment) applicationContext.getEnvironment();
        
        MutablePropertySources propertySources = env.getPropertySources();
        
        log.info("当前环境属性源 ({} 个):", propertySources.size());
        int index = 0;
        for (PropertySource<?> source : propertySources) {
            log.info("[{}] {}", index++, source.getName());
            
            // 显示关键属性
            if (source.getName().contains("bootstrap") || 
                source.getName().contains("application")) {
                
                log.info("  来源: {}", getPropertySourceOrigin(source));
                
                if (source instanceof MapPropertySource) {
                    MapPropertySource mapSource = (MapPropertySource) source;
                    Set<String> propertyNames = mapSource.getPropertyNames();
                    
                    // 显示一些关键属性
                    propertyNames.stream()
                        .filter(name -> name.contains("spring.datasource") ||
                                       name.contains("server.port") ||
                                       name.contains("spring.application.name"))
                        .forEach(name -> 
                            log.info("    {} = {}", name, mapSource.getProperty(name))
                        );
                }
            }
        }
    }
    
    private String getPropertySourceOrigin(PropertySource<?> source) {
        try {
            Field originField = PropertySource.class.getDeclaredField("origin");
            originField.setAccessible(true);
            Object origin = originField.get(source);
            return origin != null ? origin.toString() : "unknown";
        } catch (Exception e) {
            return "unknown";
        }
    }
    
    private void checkConfigurationConflicts() {
        log.info("=== 配置冲突检查 ===");
        
        // 检查常见的配置冲突
        String[] conflictingProperties = {
            "spring.datasource.url",
            "spring.datasource.username",
            "server.port",
            "spring.application.name",
            "spring.profiles.active"
        };
        
        for (String property : conflictingProperties) {
            String value = applicationContext.getEnvironment().getProperty(property);
            log.info("{} = {}", property, value);
            
            // 检查属性来源
            findPropertySource(property);
        }
    }
    
    private void findPropertySource(String propertyName) {
        ConfigurableEnvironment env = 
            (ConfigurableEnvironment) applicationContext.getEnvironment();
        
        for (PropertySource<?> source : env.getPropertySources()) {
            if (source.containsProperty(propertyName)) {
                log.debug("  属性 {} 来自: {}", propertyName, source.getName());
            }
        }
    }
    
    private void checkDuplicateContexts() {
        Set<String> contextIds = new HashSet<>();
        ApplicationContext current = applicationContext;
        
        while (current != null) {
            if (!contextIds.add(current.getId())) {
                log.error("❌ 发现重复的上下文ID: {}", current.getId());
            }
            current = current.getParent();
        }
    }
}

2.3 Bootstrap上下文配置跟踪器

@Component
public class BootstrapContextTracker {
    
    private static final Logger log = LoggerFactory.getLogger(BootstrapContextTracker.class);
    
    private static final List<ApplicationContext> bootstrapContexts = new ArrayList<>();
    private static final Map<String, BootstrapContextInfo> contextInfoMap = new HashMap<>();
    
    // 静态方法,用于在Bootstrap上下文创建时记录
    public static void trackBootstrapContext(ApplicationContext context) {
        bootstrapContexts.add(context);
        
        BootstrapContextInfo info = new BootstrapContextInfo();
        info.setId(context.getId());
        info.setStartTime(System.currentTimeMillis());
        info.setBeanCount(context.getBeanDefinitionNames().length);
        
        if (context instanceof ConfigurableApplicationContext) {
            ConfigurableEnvironment env = 
                ((ConfigurableApplicationContext) context).getEnvironment();
            info.setActiveProfiles(env.getActiveProfiles());
            info.setPropertySources(getPropertySourceNames(env));
        }
        
        contextInfoMap.put(context.getId(), info);
        
        log.info("📌 记录Bootstrap上下文: {}", context.getId());
    }
    
    @PostConstruct
    public void reportBootstrapContexts() {
        log.info("=== Bootstrap上下文追踪报告 ===");
        log.info("发现的Bootstrap上下文数量: {}", bootstrapContexts.size());
        
        contextInfoMap.forEach((id, info) -> {
            log.info("上下文ID: {}", id);
            log.info("  启动时间: {}", new Date(info.getStartTime()));
            log.info("  Bean数量: {}", info.getBeanCount());
            log.info("  活跃Profiles: {}", Arrays.toString(info.getActiveProfiles()));
            log.info("  属性源: {}", info.getPropertySources());
        });
        
        // 检查与主上下文的冲突
        checkConflictsWithMainContext();
    }
    
    private void checkConflictsWithMainContext() {
        if (applicationContext != null) {
            ApplicationContext mainContext = applicationContext;
            
            // 查找父上下文中的Bootstrap上下文
            ApplicationContext parent = mainContext.getParent();
            while (parent != null) {
                if (contextInfoMap.containsKey(parent.getId())) {
                    log.info("主上下文的父上下文是Bootstrap上下文: {}", parent.getId());
                    compareContexts(parent, mainContext);
                }
                parent = parent.getParent();
            }
        }
    }
    
    private void compareContexts(ApplicationContext bootstrap, 
                                 ApplicationContext main) {
        // 比较Bean定义
        Set<String> bootstrapBeans = new HashSet<>(Arrays.asList(bootstrap.getBeanDefinitionNames()));
        Set<String> mainBeans = new HashSet<>(Arrays.asList(main.getBeanDefinitionNames()));
        
        // 找出重复的Bean
        Set<String> duplicateBeans = new HashSet<>(bootstrapBeans);
        duplicateBeans.retainAll(mainBeans);
        
        if (!duplicateBeans.isEmpty()) {
            log.warn("⚠️ 发现重复的Bean定义 ({} 个):", duplicateBeans.size());
            duplicateBeans.forEach(bean -> 
                log.warn("  - {}", bean)
            );
        }
        
        // 比较属性
        compareProperties(bootstrap, main);
    }
    
    private void compareProperties(ApplicationContext bootstrap, 
                                   ApplicationContext main) {
        if (bootstrap instanceof ConfigurableApplicationContext && 
            main instanceof ConfigurableApplicationContext) {
            
            ConfigurableEnvironment bootstrapEnv = 
                ((ConfigurableApplicationContext) bootstrap).getEnvironment();
            ConfigurableEnvironment mainEnv = 
                ((ConfigurableApplicationContext) main).getEnvironment();
            
            String[] keyProperties = {
                "spring.datasource.url",
                "spring.datasource.username",
                "server.port",
                "spring.application.name"
            };
            
            for (String property : keyProperties) {
                String bootstrapValue = bootstrapEnv.getProperty(property);
                String mainValue = mainEnv.getProperty(property);
                
                if (bootstrapValue != null && mainValue != null && 
                    !bootstrapValue.equals(mainValue)) {
                    log.warn("⚠️ 属性值冲突: {}", property);
                    log.warn("  Bootstrap值: {}", bootstrapValue);
                    log.warn("  主上下文值: {}", mainValue);
                }
            }
        }
    }
    
    private static List<String> getPropertySourceNames(ConfigurableEnvironment env) {
        List<String> names = new ArrayList<>();
        env.getPropertySources().forEach(source -> names.add(source.getName()));
        return names;
    }
    
    // 注册为BeanFactoryPostProcessor,在早期阶段捕获
    @Component
    public static class BootstrapContextDetector implements BeanFactoryPostProcessor {
        
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
                throws BeansException {
            
            ApplicationContext context = getApplicationContext(beanFactory);
            if (context != null && isBootstrapContext(context)) {
                BootstrapContextTracker.trackBootstrapContext(context);
            }
        }
        
        private ApplicationContext getApplicationContext(ConfigurableListableBeanFactory beanFactory) {
            if (beanFactory instanceof ApplicationContext) {
                return (ApplicationContext) beanFactory;
            }
            return null;
        }
        
        private boolean isBootstrapContext(ApplicationContext context) {
            // 根据特征判断是否是Bootstrap上下文
            String id = context.getId();
            return id != null && 
                   (id.toLowerCase().contains("bootstrap") ||
                    id.contains("parent") ||
                    context.getParent() == null && context.getId().contains("application"));
        }
    }
    
    // 信息类
    static class BootstrapContextInfo {
        private String id;
        private long startTime;
        private int beanCount;
        private String[] activeProfiles;
        private List<String> propertySources;
        
        // getters and setters
    }
}

三、解决方案

3.1 解决方案1:明确上下文职责与隔离

3.1.1 清晰划分上下文职责
// Bootstrap上下文配置 - 只负责基础设施
@Configuration
public class BootstrapInfrastructureConfig {
    
    // ✅ 正确:Bootstrap中只配置基础设施
    @Bean
    public PropertySourcesPlaceholderConfigurer bootstrapPropertyConfigurer() {
        PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
        configurer.setLocations(new ClassPathResource("bootstrap.properties"));
        configurer.setIgnoreUnresolvablePlaceholders(true);
        return configurer;
    }
    
    @Bean
    public Environment bootstrapEnvironment() {
        // 专门为Bootstrap环境配置
        return new StandardEnvironment();
    }
    
    // ❌ 避免:不要在Bootstrap中配置业务Bean
    // @Bean
    // public DataSource dataSource() { ... }
    
    // ✅ 正确:可以配置配置中心客户端等基础设施
    @Bean
    @ConditionalOnClass(name = "org.springframework.cloud.config.client.ConfigServicePropertySourceLocator")
    public ConfigServicePropertySourceLocator configServicePropertySourceLocator() {
        return new ConfigServicePropertySourceLocator();
    }
}

// 主上下文配置 - 负责业务逻辑
@SpringBootApplication
public class MainApplicationConfig {
    
    // ✅ 正确:在主上下文中配置业务Bean
    @Bean
    public DataSource dataSource(
            @Value("${spring.datasource.url}") String url,
            @Value("${spring.datasource.username}") String username) {
        return DataSourceBuilder.create()
            .url(url)
            .username(username)
            .build();
    }
    
    @Bean
    public MyService myService(DataSource dataSource) {
        return new MyService(dataSource);
    }
    
    // ✅ 正确:使用@Primary解决可能的冲突
    @Bean
    @Primary
    @ConfigurationProperties("app.datasource")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
}
3.1.2 使用Profile隔离配置
# bootstrap.yml - Bootstrap特定配置
spring:
  application:
    name: myapp-bootstrap
  cloud:
    config:
      uri: http://config-server:8888
      fail-fast: true
  profiles:
    active: bootstrap  # 专门为Bootstrap设置的Profile

# application.yml - 主应用配置
spring:
  application:
    name: myapp
  profiles:
    active: ${APP_PROFILE:default}
  config:
    use-legacy-processing: false  # 禁用传统处理方式
// 使用@Profile明确配置类的作用域
@Configuration
@Profile("bootstrap")  // 只在Bootstrap上下文中激活
public class BootstrapOnlyConfig {
    
    @Bean
    public BootstrapService bootstrapService() {
        return new BootstrapService();
    }
}

@Configuration
@Profile("!bootstrap")  // 不在Bootstrap上下文中激活
public class MainAppOnlyConfig {
    
    @Bean
    public MainService mainService() {
        return new MainService();
    }
}

// 条件化Bean创建
@Configuration
public class ConditionalBeanConfig {
    
    @Bean
    @ConditionalOnMissingBean  // 只有不存在时才创建
    public DataSource fallbackDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }
    
    @Bean
    @ConditionalOnBean(name = "bootstrapDataSource")  // 依赖Bootstrap中的Bean
    public DataSourceRouter dataSourceRouter(
            @Qualifier("bootstrapDataSource") DataSource bootstrapDs,
            DataSource fallbackDataSource) {
        
        return new DataSourceRouter(bootstrapDs, fallbackDataSource);
    }
}

3.2 解决方案2:属性源优先级管理

3.2.1 明确属性加载顺序
@Component
public class PropertySourcePriorityManager implements EnvironmentPostProcessor, Ordered {
    
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, 
                                       SpringApplication application) {
        
        log.info("管理属性源优先级...");
        
        MutablePropertySources propertySources = environment.getPropertySources();
        
        // 1. 确保Bootstrap属性在正确的位置
        reorderPropertySources(propertySources);
        
        // 2. 移除冲突的属性源
        removeConflictingPropertySources(propertySources);
        
        // 3. 添加明确的优先级标记
        markPropertySourcePriorities(propertySources);
        
        // 4. 验证属性源顺序
        validatePropertySourceOrder(propertySources);
    }
    
    private void reorderPropertySources(MutablePropertySources propertySources) {
        List<PropertySource<?>> sources = new ArrayList<>();
        propertySources.forEach(sources::add);
        
        // 重新排序:应用配置 > Bootstrap配置 > 默认配置
        propertySources.removeIf(ps -> true);  // 清空
        
        // 按优先级重新添加
        sources.sort((ps1, ps2) -> {
            int priority1 = getPropertySourcePriority(ps1.getName());
            int priority2 = getPropertySourcePriority(ps2.getName());
            return Integer.compare(priority1, priority2);
        });
        
        sources.forEach(propertySources::addLast);
    }
    
    private int getPropertySourcePriority(String name) {
        if (name.contains("applicationConfig")) return 1;
        if (name.contains("bootstrap")) return 2;
        if (name.contains("default")) return 3;
        if (name.contains("systemEnvironment")) return 4;
        if (name.contains("systemProperties")) return 5;
        return 99;
    }
    
    private void removeConflictingPropertySources(MutablePropertySources propertySources) {
        Set<String> seenProperties = new HashSet<>();
        List<PropertySource<?>> toRemove = new ArrayList<>();
        
        for (PropertySource<?> source : propertySources) {
            if (source instanceof MapPropertySource) {
                MapPropertySource mapSource = (MapPropertySource) source;
                
                // 检查是否有重复属性
                for (String propertyName : mapSource.getPropertyNames()) {
                    if (seenProperties.contains(propertyName)) {
                        log.warn("发现重复属性: {} 在属性源: {}", propertyName, source.getName());
                        // 根据规则决定是否移除
                        if (shouldRemovePropertySource(source, propertyName)) {
                            toRemove.add(source);
                            break;
                        }
                    } else {
                        seenProperties.add(propertyName);
                    }
                }
            }
        }
        
        toRemove.forEach(propertySources::remove);
    }
    
    private boolean shouldRemovePropertySource(PropertySource<?> source, String propertyName) {
        // 移除规则:保留高优先级的属性源
        String sourceName = source.getName();
        if (sourceName.contains("bootstrap") && propertyName.startsWith("spring.")) {
            // Bootstrap中的spring.*属性通常优先级较低
            return true;
        }
        return false;
    }
    
    private void markPropertySourcePriorities(MutablePropertySources propertySources) {
        int index = 0;
        for (PropertySource<?> source : propertySources) {
            log.info("属性源 [{}]: {} (优先级: {})", 
                index++, source.getName(), getPropertySourcePriority(source.getName()));
        }
    }
    
    private void validatePropertySourceOrder(MutablePropertySources propertySources) {
        // 验证关键属性的来源
        String[] keyProperties = {
            "spring.datasource.url",
            "spring.application.name",
            "server.port"
        };
        
        for (String property : keyProperties) {
            PropertySource<?> source = propertySources.stream()
                .filter(ps -> ps.containsProperty(property))
                .findFirst()
                .orElse(null);
            
            if (source != null) {
                log.info("属性 {} 来自: {}", property, source.getName());
            }
        }
    }
    
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 100;
    }
}
3.2.2 使用PropertySource自定义合并策略
@Configuration
public class CustomPropertySourceMerger {
    
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(
            ConfigurableEnvironment environment) {
        
        PropertySourcesPlaceholderConfigurer configurer = 
            new PropertySourcesPlaceholderConfigurer();
        
        // 自定义属性源合并
        CompositePropertySource compositeSource = new CompositePropertySource("merged");
        
        // 按照特定顺序合并属性源
        mergePropertySources(environment, compositeSource);
        
        configurer.setPropertySources(compositeSource);
        configurer.setIgnoreUnresolvablePlaceholders(true);
        
        return configurer;
    }
    
    private static void mergePropertySources(ConfigurableEnvironment environment, 
                                            CompositePropertySource compositeSource) {
        
        MutablePropertySources sources = environment.getPropertySources();
        Map<String, Object> mergedProperties = new HashMap<>();
        
        // 定义合并策略:后出现的覆盖先出现的
        sources.forEach(source -> {
            if (source instanceof MapPropertySource) {
                MapPropertySource mapSource = (MapPropertySource) source;
                mapSource.getSource().forEach(mergedProperties::put);
            }
        });
        
        // 添加特殊的合并逻辑
        applySpecialMergeRules(mergedProperties);
        
        compositeSource.addPropertySource(
            new MapPropertySource("mergedProperties", mergedProperties)
        );
    }
    
    private static void applySpecialMergeRules(Map<String, Object> properties) {
        // 规则1:如果同时存在bootstrap和application的配置,以application为准
        String bootstrapUrl = (String) properties.get("bootstrap.spring.datasource.url");
        String appUrl = (String) properties.get("spring.datasource.url");
        
        if (bootstrapUrl != null && appUrl != null && !bootstrapUrl.equals(appUrl)) {
            log.info("检测到配置冲突,使用application配置覆盖bootstrap配置");
            properties.remove("bootstrap.spring.datasource.url");
        }
        
        // 规则2:合并数组类型的属性
        mergeArrayProperties(properties);
    }
    
    private static void mergeArrayProperties(Map<String, Object> properties) {
        // 合并多个来源的数组属性
    }
}

3.3 解决方案3:Spring Cloud Bootstrap上下文迁移

3.3.1 从Bootstrap上下文迁移到Config Data API
# 旧方式:使用bootstrap.yml
# bootstrap.yml (已废弃)
spring:
  cloud:
    config:
      uri: http://config-server:8888
      name: myapp
      profile: dev

# 新方式:使用application.yml + Config Data API
# application.yml (Spring Boot 2.4+)
spring:
  config:
    import: optional:configserver:http://config-server:8888
    # 或者使用多个配置源
    import: 
      - optional:configserver:http://config-server:8888
      - optional:file:./local-config/
      - classpath:application-default.yml
// 迁移助手:自动检测并迁移Bootstrap配置
@Component
public class BootstrapMigrationAssistant implements EnvironmentPostProcessor, Ordered {
    
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, 
                                       SpringApplication application) {
        
        // 检测是否使用了旧的Bootstrap配置
        if (hasLegacyBootstrapConfig(environment)) {
            log.warn("检测到旧版Bootstrap配置,建议迁移到Config Data API");
            
            // 自动迁移配置
            migrateBootstrapConfig(environment);
            
            // 提示用户
            log.info("已自动迁移Bootstrap配置,请考虑更新配置文件");
            log.info("参考文档:https://spring.io/blog/2020/10/27/spring-cloud-2020-0-0-aka-ilford-is-available");
        }
    }
    
    private boolean hasLegacyBootstrapConfig(ConfigurableEnvironment environment) {
        // 检查是否有Bootstrap相关的配置
        return environment.containsProperty("spring.cloud.config.uri") ||
               environment.containsProperty("spring.cloud.bootstrap.enabled") ||
               new ClassPathResource("bootstrap.yml").exists() ||
               new ClassPathResource("bootstrap.properties").exists();
    }
    
    private void migrateBootstrapConfig(ConfigurableEnvironment environment) {
        Properties migratedProps = new Properties();
        
        // 迁移配置中心配置
        migrateConfigServerConfig(environment, migratedProps);
        
        // 迁移其他Bootstrap配置
        migrateOtherBootstrapConfig(environment, migratedProps);
        
        // 添加到环境
        if (!migratedProps.isEmpty()) {
            environment.getPropertySources().addFirst(
                new PropertiesPropertySource("migratedBootstrap", migratedProps)
            );
        }
    }
    
    private void migrateConfigServerConfig(ConfigurableEnvironment environment, 
                                          Properties migratedProps) {
        
        String configUri = environment.getProperty("spring.cloud.config.uri");
        String configName = environment.getProperty("spring.cloud.config.name");
        String configProfile = environment.getProperty("spring.cloud.config.profile");
        
        if (configUri != null) {
            // 构建Config Data导入语句
            StringBuilder importStatement = new StringBuilder("optional:configserver:");
            importStatement.append(configUri);
            
            if (configName != null || configProfile != null) {
                importStatement.append("/").append(configName != null ? configName : "application");
                if (configProfile != null) {
                    importStatement.append("/").append(configProfile);
                }
            }
            
            migratedProps.setProperty("spring.config.import", importStatement.toString());
            log.info("迁移Config Server配置: {}", importStatement);
        }
    }
    
    private void migrateOtherBootstrapConfig(ConfigurableEnvironment environment, 
                                            Properties migratedProps) {
        // 迁移其他配置
        String[] bootstrapProps = {
            "spring.cloud.config.fail-fast",
            "spring.cloud.config.username",
            "spring.cloud.config.password"
        };
        
        for (String prop : bootstrapProps) {
            String value = environment.getProperty(prop);
            if (value != null) {
                migratedProps.setProperty(prop, value);
            }
        }
    }
    
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
3.3.2 使用Config Data API的兼容层
@Configuration
public class ConfigDataCompatibilityLayer {
    
    // 为需要Bootstrap上下文的老代码提供兼容层
    @Bean
    @ConditionalOnMissingBean
    public BootstrapContextCompatibilityService bootstrapCompatibilityService(
            Environment environment) {
        
        return new BootstrapContextCompatibilityService(environment);
    }
    
    // 模拟Bootstrap上下文的PropertySource
    @Bean
    public PropertySource<?> bootstrapPropertySourceEmulator() {
        Map<String, Object> bootstrapProperties = new HashMap<>();
        
        // 从Config Data中提取相当于Bootstrap的属性
        bootstrapProperties.put("bootstrap.enabled", true);
        bootstrapProperties.put("spring.cloud.bootstrap.sources", "compatibility");
        
        return new MapPropertySource("bootstrapEmulator", bootstrapProperties);
    }
    
    // 兼容性服务
    static class BootstrapContextCompatibilityService {
        
        private final Environment environment;
        
        public BootstrapContextCompatibilityService(Environment environment) {
            this.environment = environment;
        }
        
        public String getBootstrapProperty(String key) {
            // 尝试从多个来源获取属性
            String value = environment.getProperty(key);
            
            if (value == null) {
                // 尝试添加bootstrap前缀
                value = environment.getProperty("bootstrap." + key);
            }
            
            return value;
        }
        
        public boolean isBootstrapEnabled() {
            return Boolean.parseBoolean(
                environment.getProperty("spring.cloud.bootstrap.enabled", "false"));
        }
    }
}

// 使用兼容层的老代码
@Component
public class LegacyBootstrapAwareComponent {
    
    @Autowired
    private BootstrapContextCompatibilityService compatibilityService;
    
    @PostConstruct
    public void init() {
        if (compatibilityService.isBootstrapEnabled()) {
            // 使用Bootstrap属性
            String configValue = compatibilityService.getBootstrapProperty("my.config");
            // 处理逻辑
        }
    }
}

3.4 解决方案4:自定义上下文层次结构

3.4.1 创建明确的上下文层次
// 自定义的上下文层次构建器
@Component
public class CustomContextHierarchyBuilder {
    
    public ConfigurableApplicationContext createHierarchy() {
        // 1. 创建根上下文(Bootstrap)
        AnnotationConfigApplicationContext bootstrapContext = 
            new AnnotationConfigApplicationContext();
        bootstrapContext.setId("bootstrapContext");
        bootstrapContext.register(BootstrapConfig.class);
        bootstrapContext.refresh();
        
        // 2. 创建子上下文(主应用)
        SpringApplicationBuilder builder = new SpringApplicationBuilder(MainApplication.class)
            .parent(bootstrapContext)  // 设置父上下文
            .contextClass(AnnotationConfigServletWebServerApplicationContext.class)
            .registerShutdownHook(true);
        
        // 3. 配置子上下文
        configureChildContext(builder);
        
        return builder.run();
    }
    
    private void configureChildContext(SpringApplicationBuilder builder) {
        builder.properties(
            "spring.main.allow-bean-definition-overriding=true",
            "spring.main.allow-circular-references=true"
        );
        
        // 添加监听器来处理上下文层次
        builder.listeners(new ContextHierarchyListener());
    }
    
    // 上下文层次监听器
    static class ContextHierarchyListener implements ApplicationListener<ApplicationEvent> {
        
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            if (event instanceof ApplicationPreparedEvent) {
                ApplicationPreparedEvent preparedEvent = (ApplicationPreparedEvent) event;
                ConfigurableApplicationContext context = preparedEvent.getApplicationContext();
                
                log.info("上下文层次建立完成");
                log.info("当前上下文: {}", context.getId());
                log.info("父上下文: {}", 
                    context.getParent() != null ? context.getParent().getId() : "无");
                
                // 验证Bean定义不冲突
                validateBeanDefinitions(context);
            }
        }
        
        private void validateBeanDefinitions(ConfigurableApplicationContext context) {
            Set<String> allBeans = new HashSet<>();
            
            // 收集所有上下文的Bean
            ApplicationContext current = context;
            while (current != null) {
                String[] beanNames = current.getBeanDefinitionNames();
                for (String beanName : beanNames) {
                    if (!allBeans.add(beanName)) {
                        log.warn("发现重复的Bean定义: {} 在上下文: {}", 
                            beanName, current.getId());
                    }
                }
                current = current.getParent();
            }
        }
    }
}

// Bootstrap配置
@Configuration
@Profile("bootstrap")
public class BootstrapConfig {
    
    @Bean
    public BootstrapService bootstrapService() {
        return new BootstrapService();
    }
    
    // 使用不同的Bean名称避免冲突
    @Bean("bootstrapDataSource")
    public DataSource bootstrapDataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:h2:mem:bootstrap")
            .build();
    }
}

// 主应用配置
@SpringBootApplication
public class MainApplication {
    
    // 使用@Qualifier区分不同上下文的Bean
    @Bean
    public MainService mainService(
            @Qualifier("bootstrapDataSource") DataSource bootstrapDs) {
        return new MainService(bootstrapDs);
    }
    
    // 主上下文的DataSource,使用不同的名称
    @Bean("mainDataSource")
    @Primary  // 标记为主数据源
    public DataSource mainDataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:h2:mem:main")
            .build();
    }
}
3.4.2 使用DelegatingApplicationContext
// 委托上下文,用于桥接Bootstrap和主上下文
public class DelegatingApplicationContext implements ApplicationContext {
    
    private final ApplicationContext bootstrapContext;
    private final ApplicationContext mainContext;
    private final BeanResolver beanResolver;
    
    public DelegatingApplicationContext(ApplicationContext bootstrapContext, 
                                        ApplicationContext mainContext) {
        this.bootstrapContext = bootstrapContext;
        this.mainContext = mainContext;
        this.beanResolver = new CompositeBeanResolver(bootstrapContext, mainContext);
    }
    
    @Override
    public Object getBean(String name) throws BeansException {
        // 先在主上下文中查找
        try {
            return mainContext.getBean(name);
        } catch (NoSuchBeanDefinitionException e) {
            // 主上下文没有,再在Bootstrap上下文中查找
            return bootstrapContext.getBean(name);
        }
    }
    
    @Override
    public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        // 类似的委托逻辑
        try {
            return mainContext.getBean(name, requiredType);
        } catch (NoSuchBeanDefinitionException e) {
            return bootstrapContext.getBean(name, requiredType);
        }
    }
    
    @Override
    public <T> T getBean(Class<T> requiredType) throws BeansException {
        // 处理多个同类型Bean的情况
        return beanResolver.resolveBean(requiredType);
    }
    
    // 其他ApplicationContext方法的实现...
    
    // 复合Bean解析器
    static class CompositeBeanResolver {
        private final ApplicationContext[] contexts;
        
        public CompositeBeanResolver(ApplicationContext... contexts) {
            this.contexts = contexts;
        }
        
        public <T> T resolveBean(Class<T> requiredType) {
            List<T> beans = new ArrayList<>();
            
            for (ApplicationContext context : contexts) {
                try {
                    T bean = context.getBean(requiredType);
                    beans.add(bean);
                } catch (NoSuchBeanDefinitionException e) {
                    // 忽略
                }
            }
            
            if (beans.isEmpty()) {
                throw new NoSuchBeanDefinitionException(requiredType);
            }
            
            if (beans.size() > 1) {
                log.warn("找到多个 {} 类型的Bean,使用第一个", requiredType.getName());
            }
            
            return beans.get(0);
        }
    }
}

// 使用委托上下文
@Configuration
public class DelegatingContextConfig {
    
    @Bean
    public ApplicationContext delegatingApplicationContext(
            @Qualifier("bootstrapContext") ApplicationContext bootstrapContext,
            ApplicationContext mainContext) {
        
        return new DelegatingApplicationContext(bootstrapContext, mainContext);
    }
    
    @Bean
    @Qualifier("bootstrapContext")
    public ApplicationContext bootstrapContext() {
        AnnotationConfigApplicationContext context = 
            new AnnotationConfigApplicationContext();
        context.register(BootstrapOnlyConfig.class);
        context.refresh();
        context.setId("bootstrapContext");
        return context;
    }
}

3.5 解决方案5:Spring Boot 3.x特定优化

3.5.1 使用Spring Boot 3.x的ContextCustomizer
// Spring Boot 3.x的上下文定制器
@Component
public class BootstrapContextCustomizer implements ContextCustomizer {
    
    @Override
    public void customize(ConfigurableApplicationContext context) {
        if (isBootstrapContext(context)) {
            customizeBootstrapContext(context);
        } else if (isMainContext(context)) {
            customizeMainContext(context);
        }
    }
    
    private boolean isBootstrapContext(ConfigurableApplicationContext context) {
        return context.getParent() == null && 
               context.getEnvironment().getPropertySources()
                   .contains("bootstrapProperties");
    }
    
    private boolean isMainContext(ConfigurableApplicationContext context) {
        return context.getParent() != null;
    }
    
    private void customizeBootstrapContext(ConfigurableApplicationContext context) {
        log.info("定制Bootstrap上下文: {}", context.getId());
        
        // 1. 限制Bean定义
        restrictBeanDefinitions(context);
        
        // 2. 设置特定的属性源顺序
        reorderPropertySourcesForBootstrap(context);
        
        // 3. 注册Bootstrap特定的BeanPostProcessor
        registerBootstrapPostProcessors(context);
    }
    
    private void customizeMainContext(ConfigurableApplicationContext context) {
        log.info("定制主上下文: {}", context.getId());
        
        // 1. 处理从父上下文继承的Bean
        handleInheritedBeans(context);
        
        // 2. 配置Bean覆盖策略
        configureBeanOverriding(context);
        
        // 3. 设置主上下文的特定配置
        configureMainContextSpecifics(context);
    }
    
    private void restrictBeanDefinitions(ConfigurableApplicationContext context) {
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        
        // 只允许特定类型的Bean在Bootstrap中定义
        String[] beanNames = beanFactory.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            String className = beanDef.getBeanClassName();
            
            if (className != null && !isAllowedInBootstrap(className)) {
                log.warn("移除不允许在Bootstrap中定义的Bean: {}", beanName);
                beanFactory.removeBeanDefinition(beanName);
            }
        }
    }
    
    private boolean isAllowedInBootstrap(String className) {
        // 只允许基础设施相关的类
        return className.contains("PropertySource") ||
               className.contains("Environment") ||
               className.contains("Config") ||
               className.contains("Bootstrap");
    }
    
    private void handleInheritedBeans(ConfigurableApplicationContext context) {
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        
        // 检查从父上下文继承的Bean
        String[] beanNames = beanFactory.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            
            if (isInheritedFromParent(beanDef)) {
                log.info("发现从父上下文继承的Bean: {}", beanName);
                
                // 根据需要决定是否覆盖
                if (shouldOverrideInheritedBean(beanName)) {
                    log.info("将在主上下文中覆盖Bean: {}", beanName);
                }
            }
        }
    }
    
    private boolean isInheritedFromParent(BeanDefinition beanDef) {
        // 检查Bean定义是否来自父上下文
        String source = beanDef.getResourceDescription();
        return source != null && source.contains("bootstrap");
    }
    
    private boolean shouldOverrideInheritedBean(String beanName) {
        // 决定是否覆盖继承的Bean
        return beanName.contains("DataSource") ||
               beanName.contains("Service") ||
               beanName.contains("Repository");
    }
}

// 注册ContextCustomizer
@Configuration
public class ContextCustomizerConfig {
    
    @Bean
    public static ContextCustomizerFactory bootstrapContextCustomizerFactory() {
        return new ContextCustomizerFactory() {
            @Override
            public ContextCustomizer createContextCustomizer(
                    Class<?> applicationClass, ConfigurableBootstrapContext bootstrapContext) {
                return new BootstrapContextCustomizer();
            }
        };
    }
}
3.5.2 使用RuntimeHints处理Native Image中的上下文冲突
// 为Native Image优化上下文配置
@Configuration
@ImportRuntimeHints(NativeContextHints.class)
public class NativeImageCompatibleConfig {
    
    // 使用@Bean(autowireCandidate = false)控制自动装配
    @Bean(autowireCandidate = false)  // 这个Bean不会自动注入
    public DataSource bootstrapDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .generateUniqueName(true)
            .build();
    }
    
    // 主上下文的DataSource,可以自动注入
    @Bean
    @Primary
    public DataSource mainDataSource() {
        return DataSourceBuilder.create().build();
    }
}

// Native Image运行时提示
class NativeContextHints implements RuntimeHintsRegistrar {
    
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        // 注册反射信息
        hints.reflection().registerType(
            BootstrapContextCustomizer.class,
            MemberCategory.INVOKE_PUBLIC_METHODS,
            MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS
        );
        
        hints.reflection().registerType(
            DelegatingApplicationContext.class,
            MemberCategory.INVOKE_PUBLIC_METHODS,
            MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS
        );
        
        // 注册资源配置
        hints.resources().registerPattern("bootstrap*.yml");
        hints.resources().registerPattern("bootstrap*.properties");
        hints.resources().registerPattern("application*.yml");
        hints.resources().registerPattern("application*.properties");
        
        // 注册序列化
        hints.serialization().registerType(Serializable.class);
    }
}

// Native Image兼容的上下文工厂
public class NativeCompatibleApplicationContextFactory 
    implements ApplicationContextFactory {
    
    @Override
    public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
        if (isNativeImageRuntime()) {
            return createNativeOptimizedContext(webApplicationType);
        } else {
            return createStandardContext(webApplicationType);
        }
    }
    
    private ConfigurableApplicationContext createNativeOptimizedContext(
            WebApplicationType webApplicationType) {
        
        log.info("创建Native Image优化的应用上下文");
        
        // 简化的上下文层次,减少反射
        AnnotationConfigApplicationContext context = 
            new AnnotationConfigApplicationContext();
        
        // 禁用某些可能导致冲突的特性
        context.getBeanFactory().setAllowBeanDefinitionOverriding(false);
        context.getBeanFactory().setAllowCircularReferences(false);
        
        return context;
    }
    
    private ConfigurableApplicationContext createStandardContext(
            WebApplicationType webApplicationType) {
        // 标准上下文创建逻辑
        return new AnnotationConfigServletWebServerApplicationContext();
    }
    
    private boolean isNativeImageRuntime() {
        return System.getProperty("org.graalvm.nativeimage.imagecode") != null;
    }
}

四、测试策略

4.1 上下文层次测试

@SpringBootTest
@ActiveProfiles("test")
class ContextHierarchyTest {
    
    @Autowired
    private ApplicationContext applicationContext;
    
    @Test
    void testContextHierarchyExists() {
        // 验证存在上下文层次
        ApplicationContext parent = applicationContext.getParent();
        assertNotNull(parent, "应该存在父上下文");
        
        log.info("主上下文ID: {}", applicationContext.getId());
        log.info("父上下文ID: {}", parent.getId());
        
        // 验证层次结构
        assertTrue(isBootstrapContext(parent), 
            "父上下文应该是Bootstrap上下文");
    }
    
    @Test
    void testBeanDefinitionSeparation() {
        // 验证Bean定义分离
        Set<String> mainContextBeans = new HashSet<>(
            Arrays.asList(applicationContext.getBeanDefinitionNames()));
        
        ApplicationContext parent = applicationContext.getParent();
        Set<String> parentContextBeans = new HashSet<>(
            Arrays.asList(parent.getBeanDefinitionNames()));
        
        // 检查重复的Bean定义
        Set<String> duplicateBeans = new HashSet<>(mainContextBeans);
        duplicateBeans.retainAll(parentContextBeans);
        
        log.info("重复的Bean定义: {}", duplicateBeans);
        
        // 某些基础设施Bean可以重复,但业务Bean不应该
        assertTrue(duplicateBeans.stream()
            .noneMatch(name -> name.contains("Service") || 
                               name.contains("Repository")),
            "业务Bean不应该在多个上下文中定义");
    }
    
    @Test
    void testPropertySourcePriority() {
        // 验证属性源优先级
        ConfigurableEnvironment env = 
            (ConfigurableEnvironment) applicationContext.getEnvironment();
        
        String datasourceUrl = env.getProperty("spring.datasource.url");
        log.info("数据源URL: {}", datasourceUrl);
        
        // 验证属性来源
        PropertySource<?> source = env.getPropertySources().stream()
            .filter(ps -> ps.containsProperty("spring.datasource.url"))
            .findFirst()
            .orElse(null);
        
        assertNotNull(source, "应该找到属性源");
        log.info("属性来源: {}", source.getName());
        
        // 主上下文的属性应该覆盖Bootstrap的属性
        assertTrue(source.getName().contains("application") || 
                   source.getName().contains("main"),
            "应该使用主上下文的属性");
    }
    
    @Test
    void testBeanInjectionFromParentContext() {
        // 测试从父上下文注入Bean
        // 父上下文中的Bean应该可以注入到主上下文
        assertDoesNotThrow(() -> {
            Object bean = applicationContext.getBean("bootstrapService");
            assertNotNull(bean, "应该能获取父上下文中的Bean");
        });
        
        // 但主上下文的Bean不应该在父上下文中
        ApplicationContext parent = applicationContext.getParent();
        assertThrows(NoSuchBeanDefinitionException.class, () -> {
            parent.getBean("mainService");
        }, "父上下文不应该有主上下文的Bean");
    }
    
    @TestConfiguration
    @Import(TestBootstrapConfig.class)
    static class TestConfig {
    }
    
    @Configuration
    @Profile("test")
    static class TestBootstrapConfig {
        
        @Bean
        public BootstrapService bootstrapService() {
            return new BootstrapService();
        }
        
        @Bean
        public PropertySourcesPlaceholderConfigurer bootstrapPropertyConfigurer() {
            PropertySourcesPlaceholderConfigurer configurer = 
                new PropertySourcesPlaceholderConfigurer();
            
            Properties props = new Properties();
            props.setProperty("spring.datasource.url", "jdbc:h2:mem:bootstrap-test");
            
            configurer.setProperties(props);
            return configurer;
        }
    }
    
    private boolean isBootstrapContext(ApplicationContext context) {
        return context.getId() != null && 
               (context.getId().contains("bootstrap") || 
                context.getId().contains("parent"));
    }
}

4.2 集成测试

@SpringBootTest
@ExtendWith(SpringExtension.class)
@ContextConfiguration(loader = CustomContextLoader.class)
class ContextHierarchyIntegrationTest {
    
    @Autowired
    private ConfigurableApplicationContext context;
    
    @Test
    void testCustomContextHierarchy() {
        // 验证自定义的上下文层次
        ApplicationContext parent = context.getParent();
        
        assertNotNull(parent);
        assertEquals("bootstrapContext", parent.getId());
        
        // 验证两个上下文都正常
        assertTrue(parent.isActive());
        assertTrue(context.isActive());
        
        // 验证Bean定义
        assertNotNull(parent.getBean("bootstrapDataSource"));
        assertNotNull(context.getBean("mainDataSource"));
        
        // 验证属性隔离
        ConfigurableEnvironment parentEnv = 
            (ConfigurableEnvironment) parent.getEnvironment();
        ConfigurableEnvironment mainEnv = 
            (ConfigurableEnvironment) context.getEnvironment();
        
        String parentDbUrl = parentEnv.getProperty("spring.datasource.url");
        String mainDbUrl = mainEnv.getProperty("spring.datasource.url");
        
        assertNotEquals(parentDbUrl, mainDbUrl, 
            "两个上下文应该有独立的数据源配置");
    }
    
    @Test
    void testBeanOverrideBehavior() {
        // 测试Bean覆盖行为
        // 主上下文可以覆盖父上下文的Bean
        DataSource parentDs = context.getParent().getBean(DataSource.class);
        DataSource mainDs = context.getBean(DataSource.class);
        
        assertNotSame(parentDs, mainDs, 
            "主上下文应该有自己的DataSource实例");
        
        // 但注入时应该使用主上下文的Bean
        MyService service = context.getBean(MyService.class);
        assertSame(mainDs, service.getDataSource(),
            "服务应该注入主上下文的DataSource");
    }
    
    @Test
    void testProfilePropagation() {
        // 测试Profile传播
        ApplicationContext parent = context.getParent();
        
        String[] parentProfiles = parent.getEnvironment().getActiveProfiles();
        String[] mainProfiles = context.getEnvironment().getActiveProfiles();
        
        log.info("父上下文Profiles: {}", Arrays.toString(parentProfiles));
        log.info("主上下文Profiles: {}", Arrays.toString(mainProfiles));
        
        // Profile应该独立
        assertNotEquals(Arrays.asList(parentProfiles), Arrays.asList(mainProfiles));
    }
    
    @Test
    void testPropertySourceMerge() {
        // 测试属性源合并
        ConfigurableEnvironment env = 
            (ConfigurableEnvironment) context.getEnvironment();
        
        MutablePropertySources sources = env.getPropertySources();
        
        // 验证属性源顺序
        List<String> sourceNames = new ArrayList<>();
        sources.forEach(source -> sourceNames.add(source.getName()));
        
        log.info("属性源顺序: {}", sourceNames);
        
        // 主上下文的属性源应该在Bootstrap的之上
        int bootstrapIndex = -1;
        int applicationIndex = -1;
        
        for (int i = 0; i < sourceNames.size(); i++) {
            String name = sourceNames.get(i);
            if (name.contains("bootstrap")) bootstrapIndex = i;
            if (name.contains("application")) applicationIndex = i;
        }
        
        assertTrue(applicationIndex < bootstrapIndex,
            "应用配置应该在Bootstrap配置之前(优先级更高)");
    }
    
    // 自定义上下文加载器
    static class CustomContextLoader extends SpringBootContextLoader {
        
        @Override
        protected ConfigurableApplicationContext loadContext(
                String... locations) throws Exception {
            
            // 先创建Bootstrap上下文
            AnnotationConfigApplicationContext bootstrapContext = 
                new AnnotationConfigApplicationContext();
            bootstrapContext.setId("bootstrapContext");
            bootstrapContext.register(IntegrationBootstrapConfig.class);
            bootstrapContext.getEnvironment().addActiveProfile("bootstrap");
            bootstrapContext.refresh();
            
            // 创建主上下文
            SpringApplicationBuilder builder = new SpringApplicationBuilder(
                    IntegrationMainConfig.class)
                .parent(bootstrapContext)
                .profiles("integration")
                .properties(
                    "spring.main.allow-bean-definition-overriding=true",
                    "spring.main.web-application-type=none"
                );
            
            return builder.run();
        }
    }
    
    @Configuration
    @Profile("bootstrap")
    static class IntegrationBootstrapConfig {
        
        @Bean
        public DataSource bootstrapDataSource() {
            return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .setName("bootstrapDB")
                .build();
        }
        
        @Bean
        public PropertySourcesPlaceholderConfigurer bootstrapPropertyConfigurer() {
            PropertySourcesPlaceholderConfigurer configurer = 
                new PropertySourcesPlaceholderConfigurer();
            
            Properties props = new Properties();
            props.setProperty("spring.datasource.url", "jdbc:h2:mem:bootstrap-integration");
            props.setProperty("app.bootstrap.config", "loaded");
            
            configurer.setProperties(props);
            return configurer;
        }
    }
    
    @SpringBootConfiguration
    @Profile("integration")
    static class IntegrationMainConfig {
        
        @Bean
        @Primary
        public DataSource mainDataSource() {
            return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .setName("mainDB")
                .build();
        }
        
        @Bean
        public MyService myService(DataSource dataSource) {
            return new MyService(dataSource);
        }
    }
    
    static class MyService {
        private final DataSource dataSource;
        
        public MyService(DataSource dataSource) {
            this.dataSource = dataSource;
        }
        
        public DataSource getDataSource() {
            return dataSource;
        }
    }
}

五、最佳实践总结

5.1 Bootstrap上下文使用决策矩阵

# Bootstrap上下文使用决策矩阵
bootstrap-context-decision-matrix:
  
  scenario: "是否需要Bootstrap上下文?"
  decision-tree:
    - question: "是否使用Spring Cloud Config Server?"
      answer:
        yes: 
          action: "考虑使用Config Data API而不是Bootstrap上下文"
          reason: "Spring Boot 2.4+推荐使用spring.config.import"
        no: 
          next-question: "是否有复杂的属性加载顺序需求?"
    
    - question: "是否有复杂的属性加载顺序需求?"
      answer:
        yes:
          action: "可以使用Bootstrap上下文,但考虑EnvironmentPostProcessor"
          reason: "EnvironmentPostProcessor更灵活且无上下文层次问题"
        no:
          next-question: "是否需要父上下文来共享基础设施Bean?"
    
    - question: "是否需要父上下文来共享基础设施Bean?"
      answer:
        yes:
          action: "可以使用Bootstrap上下文"
          reason: "通过上下文层次共享基础设施Bean"
          considerations:
            - "明确划分Bootstrap和主上下文的职责"
            - "使用@Profile隔离配置"
            - "避免Bean名称冲突"
        no:
          action: "不需要Bootstrap上下文"
          reason: "使用单一上下文简化架构"
  
  recommendations:
    - "Spring Boot 3.x优先考虑Config Data API"
    - "如果必须使用Bootstrap上下文,保持配置简单清晰"
    - "使用@Primary和@Qualifier解决Bean冲突"
    - "为Native Image构建优化上下文配置"

5.2 问题排查流程

渲染错误: Mermaid 渲染失败: Parse error on line 13: ...J{调整依赖关系} I --> K[使用@Primary标记主Bean] ----------------------^ Expecting 'AMP', 'COLON', 'PIPE', 'TESTSTR', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'NODE_STRING', 'BRKT', 'MINUS', 'MULT', 'UNICODE_TEXT', got 'LINK_ID'

5.3 配置模板

// Spring Boot 3.x Bootstrap上下文最佳实践配置模板
@Configuration
public class BootstrapContextBestPractices {
    
    /**
     * Bootstrap上下文配置 - 最小化原则
     * 只包含必要的基础设施配置
     */
    @Configuration(proxyBeanMethods = false)
    @Profile("bootstrap")
    public static class MinimalBootstrapConfig {
        
        /**
         * ✅ 正确:Bootstrap中只配置基础设施相关的PropertySource
         */
        @Bean
        public static PropertySourcesPlaceholderConfigurer bootstrapPropertyConfigurer() {
            PropertySourcesPlaceholderConfigurer configurer = 
                new PropertySourcesPlaceholderConfigurer();
            
            // 明确指定Bootstrap属性文件
            configurer.setLocations(
                new ClassPathResource("bootstrap.properties")
            );
            
            // 关键配置:允许属性未解析,避免阻塞启动
            configurer.setIgnoreUnresolvablePlaceholders(true);
            configurer.setIgnoreResourceNotFound(true);
            
            // 设置Bootstrap特定的属性
            Properties bootstrapProperties = new Properties();
            bootstrapProperties.setProperty("spring.cloud.bootstrap.enabled", "true");
            bootstrapProperties.setProperty("app.bootstrap.loaded", "true");
            
            configurer.setProperties(bootstrapProperties);
            
            return configurer;
        }
        
        /**
         * ✅ 正确:基础设施Bean使用特定名称
         */
        @Bean("bootstrapDataSource")
        public DataSource bootstrapDataSource() {
            // 使用不同的Bean名称避免冲突
            return DataSourceBuilder.create()
                .type(EmbeddedDataSource.class)
                .build();
        }
        
        /**
         * ❌ 避免:不要在Bootstrap中定义业务Bean
         */
        // @Bean
        // public UserService userService() { ... }
    }
    
    /**
     * 主应用上下文配置 - 完整业务配置
     */
    @SpringBootApplication
    @EnableConfigurationProperties
    public static class MainApplicationConfig {
        
        /**
         * ✅ 正确:主上下文的业务Bean使用@Primary
         */
        @Bean
        @Primary  // 明确标记为主Bean
        @ConfigurationProperties("spring.datasource")
        public DataSource mainDataSource() {
            return DataSourceBuilder.create().build();
        }
        
        /**
         * ✅ 正确:使用@Qualifier引用特定Bean
         */
        @Bean
        public DataSourceRouter dataSourceRouter(
                @Qualifier("bootstrapDataSource") DataSource bootstrapDs,
                @Qualifier("mainDataSource") DataSource mainDs) {
            
            return new DataSourceRouter(bootstrapDs, mainDs);
        }
        
        /**
         * ✅ 正确:条件化配置,避免重复
         */
        @Bean
        @ConditionalOnMissingBean  // 只有不存在时才创建
        public CacheManager cacheManager() {
            return new SimpleCacheManager();
        }
        
        /**
         * ✅ 正确:处理上下文层次相关的Bean
         */
        @Bean
        public ParentContextAwareBean parentContextAwareBean(
                ApplicationContext applicationContext) {
            
            // 明确处理父上下文关系
            ApplicationContext parent = applicationContext.getParent();
            if (parent != null) {
                log.info("检测到父上下文: {}", parent.getId());
            }
            
            return new ParentContextAwareBean(parent);
        }
    }
    
    /**
     * 属性源合并配置
     */
    @Configuration
    public static class PropertySourceMergingConfig {
        
        @Bean
        public EnvironmentPostProcessor propertySourceOrderProcessor() {
            return new PropertySourceOrderProcessor();
        }
        
        static class PropertySourceOrderProcessor implements EnvironmentPostProcessor, Ordered {
            
            @Override
            public void postProcessEnvironment(ConfigurableEnvironment environment, 
                                               SpringApplication application) {
                
                MutablePropertySources sources = environment.getPropertySources();
                
                // 确保应用配置优先于Bootstrap配置
                List<PropertySource<?>> orderedSources = new ArrayList<>();
                
                sources.forEach(source -> {
                    if (source.getName().contains("application")) {
                        orderedSources.add(0, source);  // 应用配置在前
                    } else if (source.getName().contains("bootstrap")) {
                        orderedSources.add(source);     // Bootstrap配置在后
                    } else {
                        orderedSources.add(source);     // 其他保持原顺序
                    }
                });
                
                // 重新设置属性源
                sources.clear();
                orderedSources.forEach(sources::addLast);
            }
            
            @Override
            public int getOrder() {
                return Ordered.HIGHEST_PRECEDENCE;
            }
        }
    }
    
    /**
     * Native Image兼容配置
     */
    @Configuration
    @ConditionalOnNativeImage
    public static class NativeImageConfig {
        
        @Bean
        public static ContextCustomizer nativeContextCustomizer() {
            return context -> {
                // Native Image中简化上下文配置
                context.getBeanFactory().setAllowBeanDefinitionOverriding(false);
                
                // 禁用可能引起问题的特性
                if (context instanceof ConfigurableWebServerApplicationContext) {
                    ((ConfigurableWebServerApplicationContext) context)
                        .setRegisterDefaultServlet(false);
                }
            };
        }
    }
}

通过上述系统化的分析和解决方案,可以有效解决Spring Boot 3.x中Bootstrap上下文与主上下文配置冲突的问题。关键是要理解上下文层次结构的工作原理,并采用清晰的职责分离策略。在Spring Boot 3.x中,优先考虑使用Config Data API替代传统的Bootstrap上下文,以简化架构并减少冲突。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐