RFE: example needed of a spring.config.import factory that can read properties from the parent doc.

I am trying to create a spring.config.import factory like spring-cloud-config-client that is invoked similarly:

# application.properties
key1 = value1
key2 = value2 
spring.config.import = myfactory:

i.e just like spring-cloud-config-client

# application.properties
spring.cloud.config.username = johndoe
spring.cloud.config.password = johnpassword
spring.config.import = configserver:

In the factory I want to read properties key1/key2 (e.g. host/port username/password) - however spring-cloud-config-client does not do this in a simple manner.

  • if the properties were higher up in the hierarchy then binder.bind() works; from the parent doc - I always get an exception; I guess this is tricky because the parent triggered the factory but I want the factory to read existing data from the parent
  • instead of reading property values simply, spring-cloud-config-client has enormous ceremony of creating a @ConfigurationProperties sidekick, instantiating it with bind()/registerIfAbsent(), then reading the properties from the sidekick.

Is there a more straightforward technique without having to create this parallel object? spring-cloud-kubernetes also does this sidekick ceremony.

Comment From: philwebb

Thanks for the suggestion, but I think this use-case is a little too specific to include in the reference documentation, especially as it covers a number of areas of the codebase.

I'll provide an answer here instead in case anyone else has a similar problem or we want to reconsider adding the documentation later.


If you want to read a single value, you can bind it directly to a scalar type. For example, you can do the following to get key1 as a String:

context.getBinder().bind("key1", String.class).orElse(null)

Probably even better would be to group related keys using the same prefix and then bind to a map. For example, if you change your properties to:

my.key1 = value1
my.key2 = value2 

You can do:

Map<String, String> keys = context.getBinder().bind("my", Bindable.mapOf(String.class, String.class))
        .orElse(Collections.emptyMap());
String key1 = keys.get("key1");
String key2 = keys.get("key2");

If things get more complex, then having a companion object would be best, but it doesn't need to be a public class.

I've written a small demo app and pushed it to https://github.com/philwebb/scratch-boot-gh-32854 that shows the Map binding approach.

Comment From: aalba6675

@philwebb thank you - I mistakenly used the Binder that can be constructor injected instead of getting the binder from the context:

MyResolver(Log log, Binder, binder) {
    super();
   this.log = log;
    this.binder = binder; 
}

Could you explain what this Binder is used for (I mean vs context.getBinder() in an overriden method)?