spring-cloud / spring-cloud-consul

Spring Cloud Consul
http://cloud.spring.io/spring-cloud-consul/
Apache License 2.0
805 stars 543 forks source link

Spring Cloud Consul does not work well with application context hierarchies and profiles #811

Open aaronjwhiteside opened 1 year ago

aaronjwhiteside commented 1 year ago

Describe the bug Spring Cloud Consul: 3.0.4 Spring Boot: 2.5.14 Java: 11

Given the following application context hierarchy:

        final SpringApplicationBuilder parentBuilder = new SpringApplicationBuilder()
                .profiles("A")
                .web(WebApplicationType.NONE);
        parentBuilder.run(args);

        parentBuilder.child(BootstrapApplication.class)
                .web(WebApplicationType.NONE)
                .profiles("A", "B")
                .run(args)
                .close();

        parentBuilder.child(ApplicationConfiguration.class)
                .profiles("A")
                .web(WebApplicationType.SERVLET)
                .run(args);

And given that ConsulPropertySourceLocator is a singleton in the bootstrap context, when the empty parent context (parentBuilder) is loaded it's Environment contains the following ConsulPropertySource's:

0 = "config/service,A/"
1 = "config/service/"
2 = "config/application,A/"
3 = "config/application/"

This is all good and normal, but when the second context is loaded, it's Environment contains the following ConsulPropertySource's:

0 = "config/service,A/"
1 = "config/service/"
2 = "config/application,A/"
3 = "config/application/"
4 = "config/service,A/"
5 = "config/service,B/"
6 = "config/service/"
7 = "config/application,A/"
8 = "config/application,B/"
9 = "config/application/"

The problem is that the ConsulPropertySourceLocator is caching Consul context's from the previous ApplicationContext and just appending them together. And properties from the "B" profile that override properties from the "default" profile are not being picked up.

The current workaround I have, which is very much a hack is a @BoostrapConfiguration class that registers a BeanPostProcessor to wrap the ConsulPropertySourceLocator and clear() the context's before the next invocation of locate() is called.

@BootstrapConfiguration
public class FixConsulPropertySourceLocatorBootstrapConfiguration {
    @Bean
    public BeanPostProcessor fixConsulPropertySourceLocatorBeanPostProcessor() {
        return new BeanPostProcessor() {
            @Override
            public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
                if (bean instanceof ConsulPropertySourceLocator) {
                    return new FixedConsulPropertySourceLocator((ConsulPropertySourceLocator) bean);
                } else {
                    return bean;
                }
            }
        };
    }

    static class FixedConsulPropertySourceLocator implements PropertySourceLocator, ConsulConfigIndexes {
        private final ConsulPropertySourceLocator delegate;
        public FixedConsulPropertySourceLocator(final ConsulPropertySourceLocator delegate) {
            this.delegate = delegate;
        }

        @Override
        public PropertySource<?> locate(final Environment environment) {
            delegate.getContexts().clear();
            return delegate.locate(environment);
        }

        @Override
        public LinkedHashMap<String, Long> getIndexes() {
            return delegate.getIndexes();
        }
    }
}

It would be nice if this was fixed upstream and in a better manor than I have attempted here.

This then makes the ConsulPropertySource's for that Environment appear as expected:

0 = "config/service,A/"
1 = "config/service,B/"
2 = "config/service/"
3 = "config/application,A/"
4 = "config/application,B/"
5 = "config/application/"