-
Notifications
You must be signed in to change notification settings - Fork 40.8k
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 on interfaces #1254
Comments
That would completely compromise work on #1001 that rely on the presence of a standard javabean property. I also find this useful but we need to chose our camp. If we support it, we need a way to exclude unwanted fields/properties through annotations. We also could add a mode to the annotations that would provide a hint to the processor. In that mode only the field would be exposed (same for metadata harvesting) |
What about if we support field based binding if there is a getter but no setter. Or another easier option might be to allow protected setters. What I wanted was a quick way to expose configuration properties in a read-only form. Yet another option might be to allow
|
RJo on StackOverflow is interested in this functionality:
|
I am fine either way but we have to understand the consequence for the annotation processor. We can go with any crazy option as long as it's explicit (i.e. you have to enable a flag on the (reminder: we have no way to exclude a property) |
I would be happy to be able to use @ConfigurationProperties with constructor injection, so I can create immutable and encapsulated classes. |
I, for one, would like to have it working with plain fields. |
Maybe use |
BTW spring-boot documentation has an example that shows binding to field which I guess is a mistake. |
@mwisnicki Good spot on the docs. I've raised #2813 to address this. |
In order to have an "immutable" configuration properties object, I currently use a workaround with an interface, whose implementation is a "Java Bean" annotated with @ConfigurationProperties. Any shortcut would be appreciated. By the way, enabling field based injection would make a lot of sense, as the behavior would be more consistent with |
@michaeltecourt Thanks for the suggestion. Doing this in combination with Lombok's @DaTa annotation on the implementation class makes this fairly painless. |
@michaeltecourt that's not the same thing at all. With If we were enabling field access by default, all the fields of your class would be exposed which isn't what you want. The presence of accessors defines if it should be exposed or not. I think our best shot at it is to add a flag to enable field support explicitly. In that case, you're at least aware they are all exposed. |
@snicoll By "all the fields of your class would be exposed" do you mean "injectable/bindable" ? We mostly use In the end, what I find slightly confusing as a user is that you can do this :
and not this :
|
Yes, that is what I mean. I think we already covered why it is confusing (see my previous comment). I am not arguing we shouldn't do it, I am arguing it must be opt-in. |
oh just realized From a humble user standpoint, I know that Spring has implicit write access to my class attributes as soon as it's annotated with
@philwebb 's proxy based solution looks a lot like the "old" (not pejorative) property binder framework : http://pholser.github.io/property-binder/examples.html |
+1 I'd like a |
+1 from me. Setters are entirely useless for my user case and still, I should provide them. Having an immutable configuration would be a lot better. I'm not exactly sure how it can by supported sadly but it would be a lot better. |
👍 getters and setters on all my property classes... 😢 |
+1 |
I'd love to have that feature. Annotated constructor parameters should be another option, something like @JsonCreator annotation, to build actual inmutable objects. |
+1 Also like the idea of mimicking how Jackson uses |
This could be achieved by using java 8 |
+1 for immutable configuration. |
I think we should hold off on this one until we see how immutable constructor based properties work out. |
We've decided not to investigate this for now for the reason mentioned above. |
Is there a problem with this? |
@thekalinga no, that's why we've decided to close this one. We recommend using immutable configuration properties and we feel interface binding is very similar and not worth investigating any further at this point. |
@snicoll
(simplified from example)
The one is imperative and wordy the other is a succinct declaration. Are there any non apparent drawbacks that I'm missing here? Thanks EDIT: whitespace |
The drawbacks are not really technical but more to do with where we should invest our time. Our experience with immutable configuration properties has shown that there are quite a few things that we need to consider when changing the configuration properties model (and these are often quite time consuming). For example, one problem that's often overlooked is the generation of the meta-data file that's used to provide IDE support. Closing this issue doesn't mean that interface based properties are something that we'll never consider. We may well decide to re-open this one again in the future. For now though, we think that it's best if we see how useful people find constructor binder and how many problems the new code causes. |
Can this be reopened it as we already have support for constructor based immutable properties? (at least in Kotlin afaik) |
You can already do this add the following annotation to your application class: @ConfigurationPropertiesScan(basePackages = "com.github.loa") Then you can create a configuration properties class with final values like this: package com.github.loa.vault.configuration;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
/**
* Holds the configuration properties to the vault.
*/
@Getter
@ConstructorBinding
@RequiredArgsConstructor
@ConfigurationProperties("loa.vault")
public class VaultConfigurationProperties {
/**
* The unique name of the vault. This name will be saved when archiving new documents and will be used to identify
* the vault that holds a given document. Do not change this after creating the vault because the documents
* previously archived by this vault are not going to be accessible.
*/
private final String name;
/**
* If this vault should be able to modify documents (e.g. remove) after they are archived. If your vault is available
* publicly on the internet then set this to false!
*/
private final boolean modificationEnabled;
/**
* If this vault should archive new documents.
*/
private final boolean archiving;
/**
* This version number will be saved to the database for every document that has been archived. It will be used
* later on if it's necessary to run cleanup or fixing tasks that are specific to a given version.
*/
private final int versionNumber;
} Why is this not enough for your usecase? You want to set the values via reflection, without constructors? |
@dragneelfps if you're willing us to reconsider, you'd have to provide a bit more context and justification. Immutable configuration properties are available for both Java and Kotlin and this comment still stands. |
@dragneelfps You can also use records for that (or just pure immutable class like shown above). I'm not sure why you would prefer interface. |
So, I have this scenario where there is supposed to be some base config and its different child which may override and add new properties to that config, as follows http-base: &httpBase
connect-timeout: 5s
connectors:
clientA:
<<: *httpBase
clientId: foobar One way to solve is using composition instead of inheritance, but lets talk about inheritance for a bit open class Base { lateinit var connectTimeout: Duration }
class ClientA(val clientId: String): Base() The issue with above would be that the property check will throw error at runtime instead of start time when connectTimeout is missing. Also, it makes connectTimeout mutable, which I do not want. Second way would be: open class Base(val connectTimeout: Duration)
class ClientA(val clientId: String, connectTimeout: Duration): Base(connectTimeout) Now, I do not want to write connectTimeout redundantly in ClientX classes. interface Base { val connectTImeout: Duration }
class ClientA(val clientId: String, override val connectTimeout: Duration): Base Now, IMO, following would be best suited for this kind of usecase interface Base { val connectTimeout: Duration }
interface Client { val clientId: String } : Base It is clean and provides immutability and makes sense. PS: class Base(val connectTimeout: Duration)
class ClientA(val clientId: String, val baseConfig: Base) This has its own quirks, but it seems best solution compared to other currently possible ones. PS-2: |
It would be useful to allow configuration binding to work directly on fields if the user doesn't want to expose public setters.
The text was updated successfully, but these errors were encountered: