It's not possible to use @ConfigurationProperties
with third party collections such as Guava's ImmutableList.
I couldn't find an extensibility point to add one myself and lot of the code in org.springframework.boot.context.properties.bind
is package private so probably there isn't one but I'm happy to be proven wrong.
Comment From: mwisnicki
I poked around the code and it seems like one would need extension point within Binder#getAggregateBinder
.
Comment From: mwisnicki
Turns out ConfigurationPropertiesBindHandlerAdvisor
provides all that's needed.
I swap out types onStart
and then convert results in onSuccess
:
@Configuration
public class GuavaImmutableConfigurationSupport {
@Bean
ConfigurationPropertiesBindHandlerAdvisor configurationPropertiesBindHandlerAdvisor() {
return bindHandler -> new AbstractBindHandler(bindHandler) {
private final Set<ConfigurationPropertyName> immutableProperties = new HashSet<>();
private <T> Bindable<T> swapType(ConfigurationPropertyName name, Bindable<T> target, Class<?> oldClass, Class<?> newClass) {
var klass = target.getType().getRawClass();
if (oldClass.equals(klass)) {
var newType = ResolvableType.forClassWithGenerics(newClass, target.getType().getGenerics());
var newTarget = Bindable.<T>of(newType).withAnnotations(target.getAnnotations());
immutableProperties.add(name);
return newTarget;
}
return target;
}
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
var newTarget = target;
newTarget = swapType(name, newTarget, ImmutableList.class, List.class);
newTarget = swapType(name, newTarget, ImmutableSet.class, Set.class);
newTarget = swapType(name, newTarget, ImmutableMap.class, Map.class);
return super.onStart(name, newTarget, context);
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
var object = super.onSuccess(name, target, context, result);
if (immutableProperties.contains(name)) {
if (object instanceof List)
return ImmutableList.copyOf((List<?>) object);
if (object instanceof Set)
return ImmutableSet.copyOf((Set<?>) object);
if (object instanceof Map)
return ImmutableMap.copyOf((Map<?, ?>) object);
}
return object;
}
};
}
}
Comment From: mwisnicki
IMHO it would be nicer if @ConfigurationProperties
just used converters.
There's already quite a bit of code inside to handle unsupported collections and invoke conversions but it didn't work when I tried.
Comment From: philwebb
It would be quite nice to support these types, but I'm not very keen on adding Guava as a dependency. I think the ConfigurationPropertiesBindHandlerAdvisor
approach is probably the best option.
Comment From: mwisnicki
@philwebb How about making it work with registered converters?
Comment From: philwebb
Which converters did you have in mind? We do already call converters in IndexedElementsBinder
, but only if the list is defined as a complete String (i.e. prop=a,b,c will be converted but a YAML list would not).
Do you have some kind of CollectionToImmutableCollection
converter that you want to use?