Spring Boot:尝试使用 EnvironmentPostProcessor 覆盖 application.properties

Posted

技术标签:

【中文标题】Spring Boot:尝试使用 EnvironmentPostProcessor 覆盖 application.properties【英文标题】:Spring Boot: Attempting to override application.properties using EnvironmentPostProcessor 【发布时间】:2020-03-04 03:39:16 【问题描述】:

所以我尝试使用存储在 Cosul 中的键/值来覆盖 application.properties 中的值。我尝试了两件事。

1) 使用 Spring Cloud Consul 配置。 https://cloud.spring.io/spring-cloud-consul/reference/html/#spring-cloud-consul-config

如果我的 application.properties 中没有定义相同的键,这会起作用。如果它是在 application.properties 中定义的,则属性文件中的值将用于所有 @Value 注释解析。这与我想要的相反。

2) 由于上述方法不起作用,我继续创建自定义 EnvironmentPostProcessor。我首先尝试构建一个 MapPropertySource 并使用 environment.getPropertySources().addAfter(..)。这与上面的结果相同。然后我尝试遍历所有属性源,找到名称包含“applicationConfig:[classpath:/application”的属性源,并设置属性值(如果存在)或放置一个新的属性值。此外,我将 MapPropertySource 添加到“applicationConfig: [classpath:/application” 属性源所在的同一 EnumerableCompositePropertySource 中。

无论采用哪种方法,结果都是一样的。如果 key 存在于 application.properties 中,则使用该值。

什么给了?我实际上是在覆盖属性源中的值,并且在 PostProcessor 完成它的工作之前,我可以在调试器中看到这些值。 application.properties 值如何仍然到达@Value 注释?

这是我当前的后处理器。

@Order(Ordered.LOWEST_PRECEDENCE)
public class ConsulPropertyPostProcessor implements EnvironmentPostProcessor 


    private static final String PROPERTY_SOURCE_NAME = "applicationConfigurationProperties";

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) 
        PropertySource<?> system = environment.getPropertySources().get(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);

        ConsulKVService consulKVService = new ConsulKVServiceImpl().instantiateConsulKVServiceImpl((String)system.getProperty("CONSUL_HOST"), (String)system.getProperty("CONSUL_TOKEN"));
        Map<String, Object> map = consulKVService.getConsulKeysAndValuesByPrefix((String)system.getProperty("CONSUL_PREFIX"));


        addOrReplace(environment.getPropertySources(), map);
    

    private void addOrReplace(MutablePropertySources propertySources, Map<String, Object> map) 
        MapPropertySource target = new MapPropertySource("applicationConfig: [consulKVs]", map);
        if (propertySources.contains(PROPERTY_SOURCE_NAME)) 
            PropertySource<?> applicationConfigurationPropertySources = propertySources.get(PROPERTY_SOURCE_NAME);

            for(EnumerableCompositePropertySource applicationPropertySource : (ArrayList<EnumerableCompositePropertySource>)applicationConfigurationPropertySources.getSource())
                if(applicationPropertySource.getName() != null
                        && applicationPropertySource.getName().contains("applicationConfig: [profile=")) 

                    for(PropertySource singleApplicationPropertySource : applicationPropertySource.getSource())
                        if(singleApplicationPropertySource.getName().contains("applicationConfig: [classpath:/application"))

                            for (String key : map.keySet()) 
                                if(map.get(key) != null) 
                                    if (singleApplicationPropertySource.containsProperty(key)) 
                                        ((Properties) singleApplicationPropertySource.getSource())
                                                .setProperty(key, (String) map.get(key));
                                     else 
                                        ((Properties) singleApplicationPropertySource.getSource()).put(key, (String) map.get(key));
                                    
                                
                            
                            break;
                        
                    

                    applicationPropertySource.add(target);

                    break;


                
            

        

    

提前谢谢大家。

编辑: 尝试覆盖 ApplicationListener 类的 onApplicationEvent 方法,结果与上述相同。这是那个代码。

@Log4j
public class ConsulProperties implements ApplicationListener<ApplicationEnvironmentPreparedEvent> 

    static ConfigurableEnvironment configurableEnvironment;
    private static final String PROPERTY_SOURCE_NAME = "applicationConfigurationProperties";

    public static ConfigurableEnvironment getConfigurableEnvironment() 
        return configurableEnvironment;
    

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) 
        log.info("Received ApplicationEnvironmentPreparedEvent...");
        ConfigurableEnvironment environment = event.getEnvironment();
        configurableEnvironment = environment;
        Properties props = new Properties();

        ConsulKVService consulKVService = new ConsulKVServiceImpl()
                .instantiateConsulKVServiceImpl((String) configurableEnvironment.getProperty("CONSUL_HOST"),
                        (String) configurableEnvironment.getProperty("CONSUL_TOKEN"));
        Map<String, Object> map = consulKVService.getConsulKeysAndValuesByPrefix((String) configurableEnvironment.getProperty("CONSUL_PREFIX"));
        while(map.values().remove(null));
        addOrReplace(environment.getPropertySources(), map);
    


    private void addOrReplace(MutablePropertySources propertySources, Map<String, Object> map) 
        MapPropertySource target = new MapPropertySource("applicationConfig: [consulKVs]", map);
        if (propertySources.contains(PROPERTY_SOURCE_NAME)) 
            PropertySource<?> applicationConfigurationPropertySources = propertySources.get(PROPERTY_SOURCE_NAME);

            for(EnumerableCompositePropertySource applicationPropertySource : (ArrayList<EnumerableCompositePropertySource>)applicationConfigurationPropertySources.getSource())
                if(applicationPropertySource.getName() != null
                        && applicationPropertySource.getName().contains("applicationConfig: [profile=")) 

                    for(PropertySource singleApplicationPropertySource : applicationPropertySource.getSource())
                        if(singleApplicationPropertySource.getName().contains("applicationConfig: [classpath:/application"))

                            for (String key : map.keySet()) 
                                if (singleApplicationPropertySource.containsProperty(key)) 
                                    ((Properties) singleApplicationPropertySource.getSource())
                                            .setProperty(key, (String) map.get(key));
                                 else 
                                    ((Properties) singleApplicationPropertySource.getSource()).put(key,
                                            map.get(key));
                                
                            


                            applicationPropertySource.add(target);

                            Properties properties = new Properties();
                            properties.putAll(map);
                            propertySources.addLast(new PropertiesPropertySource("consulKVs", properties));

                            break;
                        
                    


                    break;


                
            
        
    

【问题讨论】:

application.properties 中定义的属性会覆盖其他来源的属性。我认为您可以使用事件侦听器来覆盖属性。检查thetechnojournals.com/2019/10/… 的事件监听器实现。 @AshokPrajapati 我尝试实现 ApplicationListener 并在 "public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) " 上使用 Override。使用与我的 OP 类似的代码来执行此操作。虽然结果相同。会将此尝试添加到 OP。 你确定你的监听器是由 Spring boot 选择并执行的吗?你是怎么注册的? @AshokPrajapati 当然可以。我使用“org.springframework.context.ApplicationListener=com.[package name removed for privacy].ConsulProperties”在 spring.factories 中注册了它,我可以看到我的“Received ApplicationEnvironmentPreparedEvent ...”日志语句并且断点在应用程序上触发开始。 我发现了一个代码问题。请查看我的最新答案。 【参考方案1】:

在您使用以下代码添加新属性源时,您的代码存在问题。请注意,当您调用“addLast”时,通过此方法添加的属性源优先级最低,并且永远不会更新已经可用的属性。

propertySources.addLast(new PropertiesPropertySource("consulKVs", properties));

您可以使用“addFirst”代替上面的代码来添加属性源,该属性源应具有最高优先级,如下面的代码所示。还有一些其他方法,例如“addAfter”和“addBefore”,您可以探索它们以在确切位置添加属性源。无论如何,“addFirst”将优先于所有其他方式,所以我认为您可以使用“addFirst”来更新属性源。

propertySources.addFirst(new PropertiesPropertySource("consulKVs", properties));

我已经使用 ApplicationEnvironmentPreparedEvent 测试了这个场景,它工作正常。希望它能解决您的问题。

【讨论】:

我切换到 addFirst 并且我仍然在我启动的控制器中看到 application.properties 值以进行快速验证。【参考方案2】:

您似乎正在尝试更改 spring 不适合的约定。您提供的代码不是那么容易维护,它需要对 Spring 内部有深入的了解。坦率地说,如果不调试如何实现您想要的,我无法判断,但是我想到了另一种方法:

您可以通过以下方式使用弹簧型材:

假设你在 application.propertiesdb.name=xyz 中有一个属性 db.name=abc 在 consul 中,我假设你的目标是在春季之前解决 db.name=xyz

在这种情况下,将 db.name=abc 移动到 application-local.properties 并使用 --spring.profiles.active=local 启动应用程序,如果您想从本地文件中获取属性,如果您想使用 consul,则不使用此配置文件。

您甚至可以在 EnvironmentPostProcessor 中动态添加一个活动配置文件(无论如何您已经到了那里),但这是 EnvironmentPostProcessor 中的一行代码。

【讨论】:

同意你的 cmets。您的“跟随方式”正是我的第一次尝试。不过,将这个故事放在我的董事会上的我的同伙并不想要这样。它们用于使用 AWS BeanStalk 中设置的环境变量覆盖 application.properties 值。我们现在达到了 BeanStalk 的 4096 字节限制,并且想要离开并将 kev/values 迁移到具有相同用例的 Consul。在 Consul 中删除 key/vlaue 以覆盖 application.properties。

以上是关于Spring Boot:尝试使用 EnvironmentPostProcessor 覆盖 application.properties的主要内容,如果未能解决你的问题,请参考以下文章

在 Tomcat 上使用 Spring Boot 进行身份验证

尝试使用 Spring Boot 使用 JMS 主题消息时出现异常

尝试通过 Spring Boot Rest 使用 Jackson 验证 JSON

尝试使用 Thymeleaf 加载 HTML 文件时出现 Spring Boot 404

迁移到 Spring Boot 2 并使用 Spring Batch 4

尝试运行 Spring Boot 应用程序时无法部署到 docker