Add possibility for mapping a property to an attribute with different name

Makes the Name annotation applicable on fields and takes this name when looking up the corresponding property.

See gh-37105
This commit is contained in:
BenchmarkingBuffalo 2024-02-08 10:36:48 +01:00
parent 500584f052
commit 2c1aced25f
4 changed files with 88 additions and 4 deletions

View File

@ -46,6 +46,7 @@ import org.springframework.core.ResolvableType;
*
* @author Phillip Webb
* @author Madhura Bhave
* @author Lasse Wulff
*/
class JavaBeanBinder implements DataObjectBinder {
@ -92,7 +93,7 @@ class JavaBeanBinder implements DataObjectBinder {
private <T> boolean bind(BeanSupplier<T> beanSupplier, DataObjectPropertyBinder propertyBinder,
BeanProperty property) {
String propertyName = property.getName();
String propertyName = determinePropertyName(property);
ResolvableType type = property.getType();
Supplier<Object> value = property.getValue(beanSupplier);
Annotation[] annotations = property.getAnnotations();
@ -110,6 +111,15 @@ class JavaBeanBinder implements DataObjectBinder {
return true;
}
private String determinePropertyName(BeanProperty property) {
return Arrays.stream((property.getAnnotations() != null) ? property.getAnnotations() : new Annotation[0])
.filter((annotation) -> annotation.annotationType() == Name.class)
.findFirst()
.map(Name.class::cast)
.map(Name::value)
.orElse(property.getName());
}
/**
* The properties of a bean that may be bound.
*/

View File

@ -23,15 +23,16 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that can be used to specify the name when binding to an immutable property.
* This annotation may be required when binding to names that clash with reserved language
* Annotation that can be used to specify the name when binding to a property. This
* annotation may be required when binding to names that clash with reserved language
* keywords.
*
* @author Phillip Webb
* @author Lasse Wulff
* @since 2.4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Documented
public @interface Name {

View File

@ -52,6 +52,7 @@ import static org.assertj.core.api.Assertions.entry;
* @author Phillip Webb
* @author Madhura Bhave
* @author Andy Wilkinson
* @author Lasse Wulff
*/
class JavaBeanBinderTests {
@ -74,6 +75,27 @@ class JavaBeanBinderTests {
assertThat(bean.getEnumValue()).isEqualTo(ExampleEnum.FOO_BAR);
}
@Test
void bindRenamedPropertyToClassBean() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("renamed.public", "alpha");
this.sources.add(source);
ExampleRenamedPropertyBean bean = this.binder.bind("renamed", Bindable.of(ExampleRenamedPropertyBean.class))
.get();
assertThat(bean.getExampleProperty()).isEqualTo("alpha");
}
@Test
void bindRenamedPropertyToRecordBean() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("renamed.class", "alpha");
this.sources.add(source);
ExampleRenamedPropertyRecordBean bean = this.binder
.bind("renamed", Bindable.of(ExampleRenamedPropertyRecordBean.class))
.get();
assertThat(bean.exampleProperty()).isEqualTo("alpha");
}
@Test
void bindToClassWhenHasNoPrefixShouldCreateBoundBean() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
@ -648,6 +670,24 @@ class JavaBeanBinderTests {
}
static class ExampleRenamedPropertyBean {
@Name("public")
private String exampleProperty;
String getExampleProperty() {
return this.exampleProperty;
}
void setExampleProperty(String exampleProperty) {
this.exampleProperty = exampleProperty;
}
}
record ExampleRenamedPropertyRecordBean(@Name("class") String exampleProperty) {
}
static class ExampleDefaultsBean {
private int foo = 123;

View File

@ -26,11 +26,13 @@ import org.springframework.context.annotation.Import
import org.springframework.test.context.support.TestPropertySourceUtils
import org.assertj.core.api.Assertions.assertThat
import org.springframework.boot.context.properties.bind.Name
/**
* Tests for {@link ConfigurationProperties @ConfigurationProperties}-annotated beans.
*
* @author Madhura Bhave
* @author Lasse Wulff
*/
class KotlinConfigurationPropertiesTests {
@ -59,6 +61,22 @@ class KotlinConfigurationPropertiesTests {
assertThat(this.context.getBean(LateInitProperties::class.java).inner.value).isEqualTo("alpha")
}
@Test
fun `renamed property can be bound`() {
this.context.register(EnableRenamedProperties::class.java)
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "renamed.fun=beta")
this.context.refresh()
assertThat(this.context.getBean(RenamedProperties::class.java).bar).isEqualTo("beta")
}
@Test
fun `renamed property can be bound to late init attribute`() {
this.context.register(EnableRenamedLateInitProperties::class.java)
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "renamed.var=beta")
this.context.refresh()
assertThat(this.context.getBean(RenamedLateInitProperties::class.java).bar).isEqualTo("beta")
}
@Test
fun `type with constructor bound lateinit property with default can be bound`() {
this.context.register(EnableLateInitPropertiesWithDefault::class.java)
@ -80,6 +98,21 @@ class KotlinConfigurationPropertiesTests {
@ConfigurationProperties(prefix = "foo")
class BingProperties(@Suppress("UNUSED_PARAMETER") bar: String)
@ConfigurationProperties(prefix = "renamed")
class RenamedProperties(@Name("fun") val bar: String)
@EnableConfigurationProperties(RenamedProperties::class)
class EnableRenamedProperties
@ConfigurationProperties(prefix = "renamed")
class RenamedLateInitProperties{
@Name("var")
lateinit var bar: String
}
@EnableConfigurationProperties(RenamedLateInitProperties::class)
class EnableRenamedLateInitProperties
@EnableConfigurationProperties
class EnableConfigProperties