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
|
* the default value for the property will not be used even if the property value is
|
||||||
* empty.
|
* empty.
|
||||||
* <p>
|
* <p>
|
||||||
* NOTE: This annotation does not support property placeholder resolution and the value
|
* Property placeholders in the default value are resolved using the {@link Binder}'s
|
||||||
* must be constant.
|
* {@link PlaceholdersResolver} before the value is converted to the property's type.
|
||||||
*
|
*
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
* @author Pavel Anisimov
|
* @author Pavel Anisimov
|
||||||
|
* @author Wan bin yu
|
||||||
* @since 2.2.0
|
* @since 2.2.0
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
|||||||
+12
-1
@@ -62,6 +62,7 @@ import org.springframework.util.Assert;
|
|||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Scott Frederick
|
* @author Scott Frederick
|
||||||
* @author Ondřej Světlík
|
* @author Ondřej Světlík
|
||||||
|
* @author Wan bin yu
|
||||||
*/
|
*/
|
||||||
class ValueObjectBinder implements DataObjectBinder {
|
class ValueObjectBinder implements DataObjectBinder {
|
||||||
|
|
||||||
@@ -135,12 +136,22 @@ class ValueObjectBinder implements DataObjectBinder {
|
|||||||
if (defaultValue.length == 0) {
|
if (defaultValue.length == 0) {
|
||||||
return getNewDefaultValueInstanceIfPossible(context, type);
|
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);
|
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,
|
private <T> @Nullable T convertDefaultValue(BindConverter converter, String[] defaultValue, ResolvableType type,
|
||||||
Annotation[] annotations) {
|
Annotation[] annotations) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+62
@@ -132,6 +132,7 @@ import static org.mockito.Mockito.mock;
|
|||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
* @author Vladislav Kisel
|
* @author Vladislav Kisel
|
||||||
|
* @author Wan bin yu
|
||||||
*/
|
*/
|
||||||
@ExtendWith(OutputCaptureExtension.class)
|
@ExtendWith(OutputCaptureExtension.class)
|
||||||
class ConfigurationPropertiesTests {
|
class ConfigurationPropertiesTests {
|
||||||
@@ -1016,6 +1017,38 @@ class ConfigurationPropertiesTests {
|
|||||||
assertThat(bean.getOptional()).isEmpty();
|
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
|
@Test
|
||||||
void loadWhenBindingToConstructorParametersWithDefaultDataUnitShouldBind() {
|
void loadWhenBindingToConstructorParametersWithDefaultDataUnitShouldBind() {
|
||||||
load(ConstructorParameterWithUnitConfiguration.class);
|
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")
|
@ConfigurationProperties("test")
|
||||||
static class ConstructorParameterEmptyDefaultValueProperties {
|
static class ConstructorParameterEmptyDefaultValueProperties {
|
||||||
|
|
||||||
|
|||||||
+26
@@ -21,6 +21,7 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -52,6 +53,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|||||||
* @author Pavel Anisimov
|
* @author Pavel Anisimov
|
||||||
* @author Yanming Zhou
|
* @author Yanming Zhou
|
||||||
* @author Ondřej Světlík
|
* @author Ondřej Světlík
|
||||||
|
* @author Wan bin yu
|
||||||
*/
|
*/
|
||||||
class ValueObjectBinderTests {
|
class ValueObjectBinderTests {
|
||||||
|
|
||||||
@@ -307,6 +309,26 @@ class ValueObjectBinderTests {
|
|||||||
assertThat(bean.getDate()).hasToString("2019-05-10");
|
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
|
@Test
|
||||||
void bindWhenAllPropertiesBoundShouldClearConfigurationProperty() { // gh-18704
|
void bindWhenAllPropertiesBoundShouldClearConfigurationProperty() { // gh-18704
|
||||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||||
@@ -639,6 +661,10 @@ class ValueObjectBinderTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record PlaceholderDefaultValue(@DefaultValue("${value}") String value) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static class ExampleDefaultValueBean {
|
static class ExampleDefaultValueBean {
|
||||||
|
|
||||||
private final int intValue;
|
private final int intValue;
|
||||||
|
|||||||
Reference in New Issue
Block a user