mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Resolve property placeholders in constructor binding default values
Resolve each annotated default using the binder placeholder resolver before conversion. Signed-off-by: Wan bin yu <3431359639@qq.com> See gh-51661
This commit is contained in:
committed by
Andy Wilkinson
parent
042c69a4f9
commit
57f1c947d1
+3
-2
@@ -33,11 +33,12 @@ import java.lang.annotation.Target;
|
||||
* the default value for the property will not be used even if the property value is
|
||||
* empty.
|
||||
* <p>
|
||||
* NOTE: This annotation does not support property placeholder resolution and the value
|
||||
* must be constant.
|
||||
* Property placeholders in the default value are resolved using the {@link Binder}'s
|
||||
* {@link PlaceholdersResolver} before the value is converted to the property's type.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Pavel Anisimov
|
||||
* @author Wan bin yu
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
+12
-1
@@ -62,6 +62,7 @@ import org.springframework.util.Assert;
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @author Ondřej Světlík
|
||||
* @author Wan bin yu
|
||||
*/
|
||||
class ValueObjectBinder implements DataObjectBinder {
|
||||
|
||||
@@ -135,12 +136,22 @@ class ValueObjectBinder implements DataObjectBinder {
|
||||
if (defaultValue.length == 0) {
|
||||
return getNewDefaultValueInstanceIfPossible(context, type);
|
||||
}
|
||||
return convertDefaultValue(context.getConverter(), defaultValue, type, annotations);
|
||||
return convertDefaultValue(context.getConverter(),
|
||||
resolveDefaultValue(context.getPlaceholdersResolver(), defaultValue), type, annotations);
|
||||
}
|
||||
}
|
||||
return context.getConverter().convert(null, type);
|
||||
}
|
||||
|
||||
private String[] resolveDefaultValue(PlaceholdersResolver resolver, String[] defaultValue) {
|
||||
String[] resolved = new String[defaultValue.length];
|
||||
for (int i = 0; i < defaultValue.length; i++) {
|
||||
Object value = resolver.resolvePlaceholders(defaultValue[i]);
|
||||
resolved[i] = String.valueOf(value);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private <T> @Nullable T convertDefaultValue(BindConverter converter, String[] defaultValue, ResolvableType type,
|
||||
Annotation[] annotations) {
|
||||
try {
|
||||
|
||||
+62
@@ -132,6 +132,7 @@ import static org.mockito.Mockito.mock;
|
||||
* @author Stephane Nicoll
|
||||
* @author Madhura Bhave
|
||||
* @author Vladislav Kisel
|
||||
* @author Wan bin yu
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class ConfigurationPropertiesTests {
|
||||
@@ -1016,6 +1017,38 @@ class ConfigurationPropertiesTests {
|
||||
assertThat(bean.getOptional()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenDefaultValuesContainPlaceholdersShouldResolveAndConvert() {
|
||||
load(PlaceholderDefaultsConfiguration.class, "defaults.name=resolved", "defaults.duration=3d",
|
||||
"defaults.count=7");
|
||||
PlaceholderDefaultsProperties bean = this.context.getBean(PlaceholderDefaultsProperties.class);
|
||||
assertThat(bean.name).isEqualTo("resolved/suffix");
|
||||
assertThat(bean.duration).isEqualTo(Duration.ofDays(3));
|
||||
assertThat(bean.counts).containsExactly(7, 2);
|
||||
assertThat(bean.names).containsExactly("resolved", "fallback");
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenDefaultValuesContainPlaceholdersShouldUsePlaceholderDefaults() {
|
||||
load(PlaceholderDefaultsConfiguration.class);
|
||||
PlaceholderDefaultsProperties bean = this.context.getBean(PlaceholderDefaultsProperties.class);
|
||||
assertThat(bean.name).isEqualTo("default/suffix");
|
||||
assertThat(bean.duration).isEqualTo(Duration.ofDays(1));
|
||||
assertThat(bean.counts).containsExactly(1, 2);
|
||||
assertThat(bean.names).containsExactly("default", "fallback");
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenExplicitValuesOverridePlaceholderDefaultsShouldUseExplicitValues() {
|
||||
load(PlaceholderDefaultsConfiguration.class, "test.name=explicit", "test.duration=5d", "test.counts=8,9",
|
||||
"test.names=one,two");
|
||||
PlaceholderDefaultsProperties bean = this.context.getBean(PlaceholderDefaultsProperties.class);
|
||||
assertThat(bean.name).isEqualTo("explicit");
|
||||
assertThat(bean.duration).isEqualTo(Duration.ofDays(5));
|
||||
assertThat(bean.counts).containsExactly(8, 9);
|
||||
assertThat(bean.names).containsExactly("one", "two");
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenBindingToConstructorParametersWithDefaultDataUnitShouldBind() {
|
||||
load(ConstructorParameterWithUnitConfiguration.class);
|
||||
@@ -2467,6 +2500,35 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(PlaceholderDefaultsProperties.class)
|
||||
static class PlaceholderDefaultsConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
static class PlaceholderDefaultsProperties {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Duration duration;
|
||||
|
||||
private final int[] counts;
|
||||
|
||||
private final List<String> names;
|
||||
|
||||
PlaceholderDefaultsProperties(@DefaultValue("${defaults.name:default}/suffix") String name,
|
||||
@DefaultValue("${defaults.duration:1d}") Duration duration,
|
||||
@DefaultValue({ "${defaults.count:1}", "2" }) int[] counts,
|
||||
@DefaultValue({ "${defaults.name:default}", "${defaults.other:fallback}" }) List<String> names) {
|
||||
this.name = name;
|
||||
this.duration = duration;
|
||||
this.counts = counts;
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
static class ConstructorParameterEmptyDefaultValueProperties {
|
||||
|
||||
|
||||
+26
@@ -21,6 +21,7 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -52,6 +53,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
* @author Pavel Anisimov
|
||||
* @author Yanming Zhou
|
||||
* @author Ondřej Světlík
|
||||
* @author Wan bin yu
|
||||
*/
|
||||
class ValueObjectBinderTests {
|
||||
|
||||
@@ -307,6 +309,26 @@ class ValueObjectBinderTests {
|
||||
assertThat(bean.getDate()).hasToString("2019-05-10");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithDefaultValuePlaceholderAndNoResolverShouldRetainPlaceholder() {
|
||||
PlaceholderDefaultValue bean = this.binder.bindOrCreate("foo", PlaceholderDefaultValue.class);
|
||||
assertThat(bean.value()).isEqualTo("${value}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithUnresolvedDefaultValuePlaceholderShouldRetainPlaceholder() {
|
||||
Binder binder = new Binder(this.sources, new PropertySourcesPlaceholdersResolver(Collections.emptyList()));
|
||||
PlaceholderDefaultValue bean = binder.bindOrCreate("foo", PlaceholderDefaultValue.class);
|
||||
assertThat(bean.value()).isEqualTo("${value}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithDefaultValuePlaceholderResolvingToNonStringShouldConvert() {
|
||||
Binder binder = new Binder(this.sources, (value) -> 42);
|
||||
PlaceholderDefaultValue bean = binder.bindOrCreate("foo", PlaceholderDefaultValue.class);
|
||||
assertThat(bean.value()).isEqualTo("42");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindWhenAllPropertiesBoundShouldClearConfigurationProperty() { // gh-18704
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
@@ -639,6 +661,10 @@ class ValueObjectBinderTests {
|
||||
|
||||
}
|
||||
|
||||
record PlaceholderDefaultValue(@DefaultValue("${value}") String value) {
|
||||
|
||||
}
|
||||
|
||||
static class ExampleDefaultValueBean {
|
||||
|
||||
private final int intValue;
|
||||
|
||||
Reference in New Issue
Block a user