Browse Source

Restore previous source in Context.withSource calls

Fix `Context.withSource` is the `Binder` to ensure that the previous
source is restored once the callback has completed.

This update is especially important in Spring Boot 3.5+ since indexed
binding calls `withSource` multiple times.

Fixes gh-46039
pull/45853/head
Phillip Webb 6 months ago
parent
commit
cb9cf45b7f
  1. 4
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java
  2. 51
      spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java

4
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -567,6 +567,7 @@ public class Binder { @@ -567,6 +567,7 @@ public class Binder {
if (source == null) {
return supplier.get();
}
ConfigurationPropertySource previous = this.source.get(0);
this.source.set(0, source);
this.sourcePushCount++;
try {
@ -574,6 +575,7 @@ public class Binder { @@ -574,6 +575,7 @@ public class Binder {
}
finally {
this.sourcePushCount--;
this.source.set(0, previous);
}
}

51
spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -123,6 +123,41 @@ class CollectionBinderTests { @@ -123,6 +123,41 @@ class CollectionBinderTests {
});
}
@Test
void bindToCollectionWhenNonKnownIndexedChildNotBoundThrowsException() {
// gh-45994
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo[0].first", "Spring");
source.put("foo[0].last", "Boot");
source.put("foo[1].missing", "bad");
this.sources.add(source);
assertThatExceptionOfType(BindException.class)
.isThrownBy(() -> this.binder.bind("foo", Bindable.listOf(Name.class)))
.satisfies((ex) -> {
Set<ConfigurationProperty> unbound = ((UnboundConfigurationPropertiesException) ex.getCause())
.getUnboundProperties();
assertThat(unbound).hasSize(1);
ConfigurationProperty property = unbound.iterator().next();
assertThat(property.getName()).hasToString("foo[1].missing");
assertThat(property.getValue()).isEqualTo("bad");
});
}
@Test
void bindToNestedCollectionWhenNonKnownIndexed() {
// gh-46039
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo[0].items[0]", "a");
source.put("foo[0].items[1]", "b");
source.put("foo[0].string", "test");
this.sources.add(source);
List<ExampleCollectionBean> list = this.binder.bind("foo", Bindable.listOf(ExampleCollectionBean.class)).get();
assertThat(list).hasSize(1);
ExampleCollectionBean bean = list.get(0);
assertThat(bean.getItems()).containsExactly("a", "b", "d");
assertThat(bean.getString()).isEqualTo("test");
}
@Test
void bindToNonScalarCollectionWhenNonSequentialShouldThrowException() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
@ -436,6 +471,8 @@ class CollectionBinderTests { @@ -436,6 +471,8 @@ class CollectionBinderTests {
private Set<String> itemsSet = new LinkedHashSet<>();
private String string;
List<String> getItems() {
return this.items;
}
@ -452,6 +489,14 @@ class CollectionBinderTests { @@ -452,6 +489,14 @@ class CollectionBinderTests {
this.itemsSet = itemsSet;
}
String getString() {
return this.string;
}
void setString(String string) {
this.string = string;
}
}
static class ExampleCustomNoDefaultConstructorBean {
@ -562,4 +607,8 @@ class CollectionBinderTests { @@ -562,4 +607,8 @@ class CollectionBinderTests {
}
record Name(String first, String last) {
}
}

Loading…
Cancel
Save