-
Notifications
You must be signed in to change notification settings - Fork 40.7k
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
Allow @ConfigurationProperties binding for immutable POJOs #8762
Comments
We currently use Spring Framework's binder (that's also used in the MVC world) and it does not support binding via constructor. I guess that's the problem you want to solve? We're rewriting the binder for Boot 2.0, perhaps @philwebb has some plans about that? |
@jhoeller just pinged me to mention SPR-15199. Would be pretty cool if we could use that somehow but I guess the best course of action is our binder and 2.0 anyway... Having said that, perhaps we can reuse some bits? |
There are two models that work with Kotlin. The first, is that a plugin model to the binder let's Kotlin describe the constructors and properties providing information about which can be used to construct the class (or a combination of constructor + properties set after). The second, is that a source of all settable things is made available and the decision is made by a Kotlin instantiator that does the work based on what it has available. The best support considers all of:
Kotlin reflection has a So the binder should give the opportunity to a plugin to do the introspection and instantiation so that the plugin can do the best job for the language. Binding examples for Kotlin: Jackson Kotlin Module (pulls parameter name information, then hands it back along with actual parameters to a custom instantiator) JDBI 3 (the Kotlin module handles all binding tasks when the target is a Kotlin class) |
If there is a branch to track for the new binder, I can look at it and see what needs to be done to support Kotlin, and add that support. |
The experimental binder code is here, but it's still very much a work in progress. The plan is to make a binder that's specifically designed for binding from our configuration properties. It'll allow us to do some nice things like track the line/column number that a property was loaded from. The Binder class is pretty complete, and there are quite a few tests. It deals with Collections, Arrays, Maps and Scalar types directly. When it finds something it can't handle it delegates to BeanBinder implementations. We currently have a We've also got plans for an @ConfigurationProperties(prefix="mine")
public interface MyProperties {
@DefaultValue("spring")
public String getName();
public int getAge();
} Which would be used to generate a proxy class with the appropriate implementation. The first step is to get the binder working with our existing mutable configuration property beans. We'll then look at extending the support. |
Quick update here: #8868 has been merged, the new binder is in master. |
@apatrida Since the new binder is now in master, any chance you could have a look to identify what needs to be done to provide first class support for immatable data classes in Kotlin? |
Is this planned for the 2.0 release or for later in the roadmap? @ConfigurationProperties are a bit confusing without it. |
To summarize the discussion I just had with @sdeleuze we have interface binding on the pipe for 2.0 (see #1254). This issue is really about supporting constructor binding as well and it brings a number of challenges. One of them is that we need to upgrade the annotation processor to be able to detect and generate the metadata in such a case. I bet Java developers will be interested by this feature as well and will use it with Lombok which will force us to detect all of these cases and I anticipate it is a lot of work. And supporting constructor binding without the metadata support is a no-go on my end. I am flagging this one for team attention so that the rest of the team has a chance to review this. |
@serandel Can you please expand a bit on what you find confusing at the moment? |
@wilkinsona Of course. :) Compare this class from my project (please, ignore the missing ElasticsearchSource definition):
to the version I tried first:
Several features are dropped:
From a Kotlin point of view, this type of configuration is obviously an immutable data class. There is some (very small, I concede) cognitive dissonance not being able to use a value type instead of a regular class for a limitation in the framework. Plus, the component is open to same rogue code altering a property. I would really love to have this issue sorted out, as it would lead to beautiful, terse, expressive and idiomatic Kotlin data classes. :) |
I just updated the previous comment. I thought that So, furthermore, we lose the distinction between optional and required properties. Perhaps it could be somewhat softened by using @required annotations and validating, I don't know, but it's not ideal. |
Comment updated 2 remarks on the provided use case:
That said, I think what makes Kotlin null-safety makes this even more needed because you can only support that correctly via constructor based injection. Otherwise you force developers to write code like A regular Java @ConfigurationProperties("mixit")
class MixitProperties(
val baseUri: String,
val admin: Credential
)
class Credential(
val username: String,
val password: String?
) A more advanced use case that will require specific Kotlin support will be classes with optional parameters and default values like this one since from a bytecode POV 2 constructors are generated: @ConfigurationProperties("mixit")
class MixitProperties(
val baseUri: String = "http://localhost:8080",
val admin: Credential
)
class Credential(
val username: String = "root",
val password: String?
) I should be able to provide the relevant utils method as part of https://jira.spring.io/browse/SPR-15673 to deal with this use case, making possible for Spring Boot to leverage it. |
FYI Spring Framework 5.0 RC3 Notice that Spring Data is about to add proper support for Kotlin constructor as well, so such support in Spring Boot for For what I have understood from previous discussion, and since we now have the confirmation that Kotlin support for immutable POJOs for I know you have a huge todo for Spring Boot 2.0, but with the guidance of somebody aware of the new |
I think the code changes for the binding won't be that tricky. The annotation processor that generates the meta-data might be a bit harder. |
There are more side effect to it. Like annotations we can only put on the getter that we'll have to accept on fields. Non scalar (nested) properties is also a challenge as soon as it's not an inner class. |
We discussed this one at our team call last week and it's still going to be a challenge in its current form. Regardless, a bean has a lifecycle and the constructor is one of them. While we don't advocate for injecting anything in a That's why I see interface-based binding as a very good way forward: we keep control over the object's lifecyle and things are more consistent with what we have now. Of course, the other options would be to stop allowing a |
After discussing with @snicoll, I agree this feature requires to stop allowing a And in fact I see at least 3 advantages to remove such capability:
I may be biased and there would be side effects as explained by @snicoll, but I think that would be a good change even if a breaking one, so I vote 👍 for removing such support from Boot 2.0. To move forward about the original need for Kotlin + Boot developers regardless of what Boot team decide on this issue, my point of view is that the most important point is to provide a solution to the current situation where both escaped This can be solved by this issue (ideal solution) but also by interface binding if Kotlin interfaces with |
Thanks a lot Boot team for fixing that! |
Re-opening so that we can update the reference docs. I’d also like to take another look at the occasional need for |
Previously, the import selector wrongly assumed that we should not use constructor injection with Kotlin. Rather than looking up for the primary constructor, we retrieve available constructors on the Java counter-part. This commit applies the same logic as in the constructor parameter binder and checks for the primary constructor for Kotlin types. See gh-8762
This commit detects a Kotlin constructor so that it is not required to transmit the parameter names information to the Java side. See spring-projectsgh-8762
This commit detects a Kotlin constructor so that it is not required to transmit the parameter names information to the Java side. See gh-8762
The documentation refers to |
In the doc there is info that this is still not supported... |
That link is for the wrong version of Spring Boot. This was updated properly: https://docs.spring.io/spring-boot/docs/2.2.0.BUILD-SNAPSHOT/reference/html/spring-boot-features.html#boot-features-kotlin-configuration-properties |
Ok, but this is first in the Google... and says that this is current. |
The |
@wilkinsona Does this work with Java aswell (or) it works just with kotlin? I tried with the following configuration & it did not resolve properties. Not sure if I had any mistake
config:
username: alpha
password: password
timeoutInSecs: 2
@Getter
@Builder
@AllArgsConstructor(onConstructor = @__(@JsonCreator))
@ToString
@ConfigurationProperties("config")
public class ExternalSystemConfig {
private String username;
private String password;
private int timeoutInSecs;
}
@Log4j2
@SpringBootApplication
@EnableConfigurationProperties(ExternalSystemConfig.class)
public class DemoApplication {
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(DemoApplication.class)
.web(WebApplicationType.NONE)
.run(args);
}
@Bean
InitializingBean configLogger(ExternalSystemConfig config) {
return () -> log.info("Config: {}", config);
}
} But the I'm using Any idea if I'm doing something wrong Thanks! |
If I make my object mutable, the binding works fine tho |
This is how my (lombok) generated class looks like @ConstructorProperties({"username", "password", "timeoutInSecs"})
@JsonCreator
public ExternalSystemConfig(String username, String password, int timeoutInSecs) {
this.username = username;
this.password = password;
this.timeoutInSecs = timeoutInSecs;
} |
@thekalinga Immutable configuration property binding works with both Java and Kotlin. If you believe you've found a situation where it's not working as expected, please open a new issue with a complete and minimal sample that reproduces the problem. A project zipped and attached to the new issue or hosted in a separate GitHub repository is ideal. |
@wilkinsona Just opened an issue with test project |
Currently, it seems we are forced to use Kotlin classes with mutable nullable properties and default constructor with
@ConfigurationProperties
while idiomatic Kotlin code would be using classes with immutable properties initialized via constructor. I think there is a way to supporting that by leveragingkotlin-reflect
library likejackson-module-kotlin
do.More concretely, in MiXiT app I would like to be able to convert this
MixitProperties
class implementation to:We could imagine to support optional properties by using nullable types, like
val sponsorform: String?
for example.I will be happy to help. I have also already worked with @apatrida who maintains Jackson Kotlin module, he may provide us some guidance I think.
The text was updated successfully, but these errors were encountered: