-
Notifications
You must be signed in to change notification settings - Fork 40.2k
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
@ConfigurationProperties creates mutable collections, even on immutable classes #27582
Comments
Turn's out it wasn't that hard to add using little known @Configuration
public class ImmutableConfigurationSupport {
@Bean
ConfigurationPropertiesBindHandlerAdvisor configurationPropertiesBindHandlerAdvisor() {
return bindHandler -> new AbstractBindHandler(bindHandler) {
int immutable = 0;
private boolean isImmutableTarget(Bindable<?> target) {
var klass = target.getType().resolve();
return klass != null && klass.isAnnotationPresent(ConstructorBinding.class);
}
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
if (isImmutableTarget(target))
immutable++;
return super.onStart(name, target, context);
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
var object = super.onSuccess(name, target, context, result);
var targetClass = target.getType().resolve();
if (immutable > 0 && targetClass != null) {
if (object instanceof List && targetClass.isAssignableFrom(List.class))
return Collections.unmodifiableList((List<?>) object);
if (object instanceof Set && targetClass.isAssignableFrom(Set.class))
return Collections.unmodifiableSet((Set<?>) object);
if (object instanceof Map && targetClass.isAssignableFrom(Map.class))
return Collections.unmodifiableMap((Map<?, ?>) object);
}
return object;
}
@Override
public void onFinish(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) throws Exception {
super.onFinish(name, target, context, result);
if (isImmutableTarget(target))
immutable--;
}
};
}
} Is this something the project would consider adding or should I create a library? |
I'm glad the We need to discuss this a bit as a team before we make any final decision. |
We discussed this today and decided that unmodifiable would be a good default but we need to provide an escape hatch for those that need mutable binding. |
Isn't |
While it can achieve the same end result, it's too low-level and verbose to recommend as a way to restore the current behaviour for users who are relying on it. |
I am interested to work in this issue. I have been working with Spring Mvc and Spring Boot based enterprise application for five years and every now and then I had a peek inside the framework code. So far I have tried cloning the code, setting up the environment, ide. Now I am trying to build it. As a first timer I may need a few feedback and acquaintance with the process to get started. |
Thanks, @rakibmail22. There's some documentation in the wiki on working with the code. For this particular issue, I think we'll need changes to We'll also need to provide a way for someone to opt out of this behaviour so that things behave as they do today. I'm not exactly sure how that should look at the moment. Perhaps an attribute on |
@wilkinsona I looked into the |
Thanks for taking a look. No conversions should be necessary if the @SuppressWarnings({ "unchecked", "rawtypes" })
private Collection<Object> unmodifiable(Collection<Object> collection) {
if (collection instanceof SortedSet) {
return Collections.unmodifiableSortedSet((SortedSet) collection);
}
if (collection instanceof Set) {
return Collections.unmodifiableSet((Set) collection);
}
if (collection instanceof List) {
return Collections.unmodifiableList((List) collection);
}
return Collections.unmodifiableCollection(collection);
} |
I am trying something similar like below.
Two test cases failed in
The first one is failing because it is exactly checking for a |
I'm not sure why merge creates a new There will be some cases where I don't think we should bind an unmodifiable collection (or Map). A property that's a To get an immutable collection or map, I think the property should be declared as a The binder's one of the most complex areas of Boot's code base so if this starts consuming more of your time than you have realised it would, please do feel free to step away. |
I can't remember the details I'm afraid. Looks like the change was made for #9290. I think we can return |
yeah, I don't think creating a new collection is necessary in the catch block. Seems like an oversight. |
Shall this be a separate issue or this change should be a part of the current one? |
@rakibmail22 It's fine to change it as part of this issue. We can always separately apply the same change to other branches if we decide that it's worth it. |
Consider simple example:
Even though I'm trying to make immutable class here, Spring will unhelpfully inject mutable
ArrayList
.Now in a very simple example like this you can of course wrap it yourself in
Collections.unmodifiableList()
but doing so quickly becomes tedious especially when you nest collection types.Imagine the code to deeply freeze e.g.
Map<String, List<String>>
:(It's even worse when you just want to use
lombok.Value
or maybe Kotlin (didn't check it) to avoid writing boilerplate completely.Not sure why these property classes should ever be mutable but if desired one can add property to
@ConfigurationProperties
to control it. It should automatically be enabled when using@ConstructorBinding
though as that's a sure sign someone is trying to make immutable properties.The text was updated successfully, but these errors were encountered: