I have a bean that implements Formatter
that is supposed to also implement Converter
as I need it to be considered for application property binding as well and I can simply forward the call to convert(…)
to the parse(…)
method of the converter.
Doing that causes the instance not to be registered as Formatter
anymore as ApplicationConversionService.addBeans(…)
uses an if-else cascade to forward the registration calls. Any particular reason it does not simply use a sequence of ifs?
Comment From: philwebb
I used the if/else cascade because I was worried about beans being registered twice. The Formatter
is especially problematic since it's also a Printer
and a Parser
.
Why do you need to have the Formatter
also be a Converter
? That's an unusual pattern, especially as the FormattingConversionService
adapts them to converters behind the scenes.
Comment From: odrotbohm
I got to this as follows:
- I have domain type
Quantity
(an amount plus some metric) for which I have aFormatter
implementation to render and parse it in and from Thymeleaf templates. - The project introduced a configuration property of the same type. I tried whether the
Formatter
would already make the binding work. It didn't. - I consulted the reference docs, learned about
@ConfigurationPropertiesBinding
simply addedConverter<String, Quantity>
to the list of interfaces implemented, added the annotation and delegated the call toconvert(…)
to the already existingparse(…)
method defaulting theLocale
to the US one. - That makes the properties binding work but breaks the rendering in templates stating that no converter from
Quantity
toString
could be found.
I fixed that be creating a separate class implementing Converter
, getting the Formatter
implementation injected and delegating to it from the outside.
While this is of course fine, I think it's problematic as it's unusual that adding an interface to a type breaks the functionality plugged in for another implemented interface. The class still is a formatter but it's not considered one anymore. I was able to find out what caused this because I remembered that the beans would be routed into the ConversionService
by such an assignment check, but someone not that deep into the internals would've had a hard time to get to the gist of it.
If you think such an arrangement is rare (which I tend to agree with) wouldn't that also mean that your original reason to use else blocks (potential double registration) is guarding a rare case? The auto configuration arrangement currently differentiates between beans of the two relevant types and thus you apparently need to register the functionality twice. The current state requires me to register two beans that could in fact be one.
Comment From: philwebb
I'd like to dig a bit more into why @ConfigurationPropertiesBinding
on the Formatter
bean didn't work. I feel like GenericConverter
, Converter
and Formatter
are different abstraction levels for the same contract, and it doesn't seem right that the Formatter
also needs to be a Converter
.
Internally we have CharArrayFormatter
and InetAddressFormatter
so I'm pretty sure that formatters can work when binding properties. Perhaps there's a bug with the way that the @ConfigurationPropertiesBinding
annotation works.
I'll try and create a sample to see if I can replicate the problem.
Comment From: odrotbohm
I can try to tweak the one I have in that direction in a couple of minutes and report back. Before you invest a lot of time in creating one from scratch.
Comment From: odrotbohm
Some more info I could gather: Adding @ConfigurationPropertiesBinding
to a Formatter
does not cause it to be used for property binding. I assume this is due to ConversionServiceDeducer.Factory
only looking up beans of type Converter
and GenericConverter
(see its constructor).
If you picked up formatters, what Locale
would be used to call the parse(…)
method? I particularly chose the US one but I can imagine others might want to use a different one and falling back to the system one would potentially change the parsing behavior when moving apps from machines to machines.
Comment From: philwebb
If you picked up formatters, what Locale would be used to call the parse(…) method?
We'd rely on the standard FormattingConversionService
behavior which uses LocaleContextHolder
.
Comment From: odrotbohm
LocalContextHolder
falls back to the system locale in case no explicit default locale is configured. Doesn't this mean that e.g. dates or numbers would be parsed differently when e.g. the application is run on a German localized system vs. a US one in production?
Comment From: philwebb
Yes, I think if your servers are set to different locales then the Formatter
would be called with different values. In your example, I suspect if you use unformatted numbers in your properties file then everything will work. Feel free to open a new issue if you think we should offer some way to override the Locale just for configuration properties.