Skip to content

@ConfigurationPropertiesBinding does not apply Formatter beans #23576

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
odrotbohm opened this issue Oct 2, 2020 · 8 comments
Closed

@ConfigurationPropertiesBinding does not apply Formatter beans #23576

odrotbohm opened this issue Oct 2, 2020 · 8 comments
Assignees
Labels
type: bug A general bug
Milestone

Comments

@odrotbohm
Copy link
Member

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?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Oct 2, 2020
odrotbohm added a commit to st-tu-dresden/salespoint that referenced this issue Oct 2, 2020
Letting QuantityFormatter implement Converter<String, Formatter> will cause the instance not registered as a formatter anymore for the following reason:

- Spring Boot's WebMvcAutoConfigurationAdapter.addFormatters(…) calls ApplicationConversionService.addBeans(…)
- That in turn uses an if-*else*-cascade to register the found beans in the Formatting registry. If the bean implements Converter, it will not see the call to ….addFormatter(…) anymore.

This has been filed in spring-projects/spring-boot#23576. As a workaround we now register a separate converter implementation that delegates to the formatter.
@philwebb
Copy link
Member

philwebb commented Oct 4, 2020

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.

@philwebb philwebb added the status: waiting-for-feedback We need additional information before we can continue label Oct 4, 2020
@odrotbohm
Copy link
Member Author

odrotbohm commented Oct 4, 2020

I got to this as follows:

  • I have domain type Quantity (an amount plus some metric) for which I have a Formatter 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 added Converter<String, Quantity> to the list of interfaces implemented, added the annotation and delegated the call to convert(…) to the already existing parse(…) method defaulting the Locale to the US one.
  • That makes the properties binding work but breaks the rendering in templates stating that no converter from Quantity to String 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.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Oct 4, 2020
@philwebb
Copy link
Member

philwebb commented Oct 4, 2020

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.

@odrotbohm
Copy link
Member Author

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.

@odrotbohm
Copy link
Member Author

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.

@philwebb philwebb changed the title ApplicationConversionService.addBeans(…) drops formatters that implement Converter @ConfigurationPropertiesBinding does not apply Formatter beans Oct 5, 2020
@philwebb philwebb added type: bug A general bug and removed status: feedback-provided Feedback has been provided status: waiting-for-triage An issue we've not yet triaged labels Oct 5, 2020
@philwebb philwebb added this to the 2.1.x milestone Oct 5, 2020
@philwebb
Copy link
Member

philwebb commented Oct 5, 2020

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.

@odrotbohm
Copy link
Member Author

odrotbohm commented Oct 7, 2020

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?

@philwebb
Copy link
Member

philwebb commented Oct 7, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants